tiger_lib/
db.rs

1//! A general database of item types. Most game items go here.
2//!
3//! Items that need special handling are stored separately in the [`Everything`] type.
4
5use std::any::Any;
6use std::fmt::Debug;
7use std::mem::take;
8
9use as_any::AsAny;
10use itertools::Itertools;
11use rayon::prelude::*;
12use strum::EnumCount;
13
14use crate::block::Block;
15use crate::context::ScopeContext;
16use crate::everything::Everything;
17use crate::game::Game;
18use crate::helpers::{TigerHashMap, TigerHashSet, dup_error, exact_dup_advice, exact_dup_error};
19use crate::item::Item;
20use crate::lowercase::Lowercase;
21#[cfg(any(feature = "vic3", feature = "eu5"))]
22use crate::report::{ErrorKey, err};
23use crate::token::Token;
24use crate::variables::Variables;
25
26pub type FlagValidator = fn(&Token, &Everything);
27
28/// The main database of game items.
29#[derive(Debug)]
30pub struct Db {
31    /// Items with full `DbEntries`, meaning a key and a block for each.
32    /// The `Vec` is indexed with an `Item` discriminant.
33    database: Vec<TigerHashMap<&'static str, DbEntry>>,
34    /// Items generated as side effects of the full items in `database`.
35    /// It allows only a limited form of validation.
36    /// The `Vec` is indexed with an `Item` discriminant.
37    flags: Vec<(TigerHashSet<Token>, Option<FlagValidator>)>,
38    /// Items that have object identity but no usable name
39    anonymous: Vec<DbEntry>,
40    /// Lowercased registry of database items and flags, for case insensitive lookups
41    items_lc: Vec<TigerHashMap<Lowercase<'static>, &'static str>>,
42}
43
44impl Default for Db {
45    fn default() -> Self {
46        Self {
47            database: (0..Item::COUNT).map(|_| TigerHashMap::default()).collect(),
48            flags: (0..Item::COUNT).map(|_| (TigerHashSet::default(), None)).collect(),
49            anonymous: Vec::new(),
50            items_lc: (0..Item::COUNT).map(|_| TigerHashMap::default()).collect(),
51        }
52    }
53}
54
55impl Db {
56    pub fn add(&mut self, item: Item, key: Token, block: Block, kind: Box<dyn DbKind>) {
57        self.add_inner(item, key, block, kind, false);
58    }
59
60    #[allow(dead_code)]
61    pub fn add_exact_dup_ok(
62        &mut self,
63        item: Item,
64        key: Token,
65        block: Block,
66        kind: Box<dyn DbKind>,
67    ) {
68        self.add_inner(item, key, block, kind, true);
69    }
70
71    fn add_inner(
72        &mut self,
73        item: Item,
74        key: Token,
75        block: Block,
76        kind: Box<dyn DbKind>,
77        exact_dup_ok: bool,
78    ) {
79        if Game::is_vic3() || Game::is_eu5() {
80            #[cfg(any(feature = "vic3", feature = "eu5"))]
81            if let Some((prefix, name)) = key.split_once(':') {
82                if !item.injectable() {
83                    let msg = format!("cannot use prefixes with {item}");
84                    err(ErrorKey::Prefixes).msg(msg).loc(key).push();
85                    return;
86                }
87                match prefix.as_str() {
88                    "INJECT" => {
89                        if let Some(other) = self.database[item as usize].get_mut(name.as_str()) {
90                            other.inject(block, kind);
91                        } else {
92                            let msg = "injecting into a non-existing item";
93                            err(ErrorKey::Prefixes).msg(msg).loc(name).push();
94                        }
95                    }
96                    "REPLACE" => {
97                        if self.database[item as usize].contains_key(name.as_str()) {
98                            self.add_inner2(item, name, block, kind);
99                        } else {
100                            let msg = "replacing a non-existing item";
101                            err(ErrorKey::Prefixes).msg(msg).loc(name).push();
102                        }
103                    }
104                    "TRY_INJECT" => {
105                        if let Some(other) = self.database[item as usize].get_mut(name.as_str()) {
106                            other.inject(block, kind);
107                        }
108                    }
109                    "TRY_REPLACE" => {
110                        if self.database[item as usize].contains_key(name.as_str()) {
111                            self.add_inner2(item, name, block, kind);
112                        }
113                    }
114                    "REPLACE_OR_CREATE" => {
115                        self.add_inner2(item, name, block, kind);
116                    }
117                    "INJECT_OR_CREATE" => {
118                        if let Some(other) = self.database[item as usize].get_mut(name.as_str()) {
119                            other.inject(block, kind);
120                        } else {
121                            self.add_inner2(item, name, block, kind);
122                        }
123                    }
124                    _ => {
125                        let msg = format!("unknown prefix `{prefix}`");
126                        err(ErrorKey::Prefixes).msg(msg).loc(prefix).push();
127                    }
128                }
129            } else {
130                #[allow(clippy::collapsible_else_if)]
131                if item.injectable() {
132                    if let Some(other) = self.database[item as usize].get(key.as_str()) {
133                        let msg =
134                            format!("must have a prefix such as `REPLACE:` to replace {item}");
135                        err(ErrorKey::Prefixes)
136                            .msg(msg)
137                            .loc(key)
138                            .loc_msg(&other.key, "original here")
139                            .push();
140                    } else {
141                        self.add_inner2(item, key, block, kind);
142                    }
143                } else {
144                    self.add_inner2(item, key, block, kind);
145                }
146            }
147        } else {
148            if let Some(other) = self.database[item as usize].get(key.as_str()) {
149                if other.key.loc.kind >= key.loc.kind {
150                    if other.block.equivalent(&block) {
151                        if exact_dup_ok {
152                            exact_dup_advice(&key, &other.key, &item.to_string());
153                        } else {
154                            exact_dup_error(&key, &other.key, &item.to_string());
155                        }
156                    } else {
157                        dup_error(&key, &other.key, &item.to_string());
158                    }
159                }
160            }
161            self.add_inner2(item, key, block, kind);
162        }
163    }
164
165    /// Actually add the item to the database, replacing any of the same name that were there before.
166    fn add_inner2(&mut self, item: Item, key: Token, block: Block, kind: Box<dyn DbKind>) {
167        self.items_lc[item as usize].insert(Lowercase::new(key.as_str()), key.as_str());
168        self.database[item as usize].insert(key.as_str(), DbEntry { key, block, kind });
169    }
170
171    #[cfg(feature = "hoi4")]
172    pub fn set_flag_validator(&mut self, item: Item, f: FlagValidator) {
173        self.flags[item as usize].1 = Some(f);
174    }
175
176    pub fn add_flag(&mut self, item: Item, key: Token) {
177        self.items_lc[item as usize].insert(Lowercase::new(key.as_str()), key.as_str());
178        self.flags[item as usize].0.insert(key);
179    }
180
181    #[cfg(feature = "hoi4")]
182    pub fn add_anonymous(&mut self, ident: Token, block: Block, kind: Box<dyn DbKind>) {
183        self.anonymous.push(DbEntry { key: ident, block, kind });
184    }
185
186    pub fn add_subitems(&mut self) {
187        for itype in 0..Item::COUNT {
188            let queue = take(&mut self.database[itype]);
189            for entry in queue.values().sorted_by_key(|e| e.key.loc) {
190                entry.kind.add_subitems(&entry.key, &entry.block, self);
191            }
192            if self.database[itype].is_empty() {
193                // The usual case. It should be extremely rare for `add_subitems` to add items of
194                // the same item type as its parent.
195                self.database[itype] = queue;
196            } else {
197                self.database[itype].extend(queue);
198            }
199        }
200    }
201
202    pub fn scan_variables(&self, registry: &mut Variables) {
203        for map in &self.database {
204            for entry in map.values() {
205                registry.scan(&entry.block);
206            }
207        }
208    }
209
210    pub fn validate(&self, data: &Everything) {
211        self.database.par_iter().for_each(|map| {
212            map.par_iter().for_each(|(_, entry)| {
213                entry.kind.validate(&entry.key, &entry.block, data);
214            });
215        });
216        self.flags.par_iter().for_each(|(map, fv)| {
217            if let Some(fv) = fv {
218                map.par_iter().for_each(|flag| {
219                    fv(flag, data);
220                });
221            }
222        });
223        self.anonymous.par_iter().for_each(|entry| {
224            entry.kind.validate(&entry.key, &entry.block, data);
225        });
226    }
227
228    pub fn exists(&self, item: Item, key: &str) -> bool {
229        self.database[item as usize].contains_key(key) || self.flags[item as usize].0.contains(key)
230    }
231
232    pub fn exists_lc(&self, item: Item, key: &Lowercase) -> bool {
233        self.items_lc[item as usize].contains_key(key)
234    }
235
236    #[allow(dead_code)]
237    pub fn get_item<T: DbKind + Any>(&self, item: Item, key: &str) -> Option<(&Token, &Block, &T)> {
238        if let Some(entry) = self.database[item as usize].get(key) {
239            if let Some(kind) = (*entry.kind).as_any().downcast_ref::<T>() {
240                return Some((&entry.key, &entry.block, kind));
241            }
242        }
243        None
244    }
245
246    pub fn get_key_block(&self, item: Item, key: &str) -> Option<(&Token, &Block)> {
247        self.database[item as usize].get(key).map(|entry| (&entry.key, &entry.block))
248    }
249
250    #[allow(dead_code)]
251    pub fn has_property(&self, item: Item, key: &str, property: &str, data: &Everything) -> bool {
252        if let Some(entry) = self.database[item as usize].get(key) {
253            entry.kind.has_property(&entry.key, &entry.block, property, data)
254        } else {
255            false
256        }
257    }
258
259    #[cfg(feature = "ck3")] // vic3 happens not to use
260    pub fn lc_has_property(
261        &self,
262        item: Item,
263        key: &Lowercase,
264        property: &str,
265        data: &Everything,
266    ) -> bool {
267        let real_key = self.items_lc[item as usize].get(key);
268        if let Some(entry) = real_key.and_then(|key| self.database[item as usize].get(key)) {
269            entry.kind.has_property(&entry.key, &entry.block, property, data)
270        } else {
271            false
272        }
273    }
274
275    #[cfg(feature = "ck3")] // vic3 happens not to use
276    pub fn set_property(&mut self, item: Item, key: &str, property: &str) {
277        if let Some(entry) = self.database[item as usize].get_mut(key) {
278            entry.kind.set_property(&entry.key, &entry.block, property);
279        }
280    }
281
282    #[allow(dead_code)]
283    pub fn validate_call(
284        &self,
285        item: Item,
286        key: &Token,
287        block: &Block,
288        data: &Everything,
289        sc: &mut ScopeContext,
290    ) {
291        if let Some(entry) = self.database[item as usize].get(key.as_str()) {
292            entry.kind.validate_call(&entry.key, &entry.block, key, block, data, sc);
293        }
294    }
295
296    #[allow(dead_code)]
297    pub fn validate_use(&self, item: Item, key: &Token, block: &Block, data: &Everything) {
298        if let Some(entry) = self.database[item as usize].get(key.as_str()) {
299            entry.kind.validate_use(&entry.key, &entry.block, data, key, block);
300        }
301    }
302
303    #[allow(dead_code)]
304    pub fn validate_property_use(
305        &self,
306        item: Item,
307        key: &Token,
308        data: &Everything,
309        property: &Token,
310        caller: &str,
311    ) {
312        if let Some(entry) = self.database[item as usize].get(key.as_str()) {
313            entry.kind.validate_property_use(&entry.key, &entry.block, property, caller, data);
314        }
315    }
316
317    #[allow(dead_code)]
318    pub fn iter_key_block(&self, itype: Item) -> impl Iterator<Item = (&Token, &Block)> {
319        self.database[itype as usize].values().map(|entry| (&entry.key, &entry.block))
320    }
321
322    pub fn iter_keys(&self, itype: Item) -> impl Iterator<Item = &Token> {
323        self.database[itype as usize]
324            .values()
325            .map(|entry| &entry.key)
326            .chain(self.flags[itype as usize].0.iter())
327    }
328}
329
330#[derive(Debug)]
331pub struct DbEntry {
332    key: Token,
333    block: Block,
334    kind: Box<dyn DbKind>,
335}
336
337impl DbEntry {
338    #[cfg(any(feature = "vic3", feature = "eu5"))]
339    fn inject(&mut self, mut block: Block, kind: Box<dyn DbKind>) {
340        self.block.append(&mut block);
341        self.kind.merge_in(kind);
342    }
343}
344
345#[allow(dead_code)]
346pub trait DbKind: Debug + AsAny + Sync + Send {
347    /// Add additional items that are implied by the current item, for example buildings that add
348    /// `BuildingFlag` items. It's done in a separate pass so that items that were later overridden
349    /// don't add their subitems.
350    fn add_subitems(&self, _key: &Token, _block: &Block, _db: &mut Db) {}
351
352    fn validate(&self, key: &Token, block: &Block, data: &Everything);
353    fn has_property(
354        &self,
355        _key: &Token,
356        _block: &Block,
357        _property: &str,
358        _data: &Everything,
359    ) -> bool {
360        false
361    }
362    fn validate_call(
363        &self,
364        _key: &Token,
365        _block: &Block,
366        _from: &Token,
367        _from_block: &Block,
368        _data: &Everything,
369        _sc: &mut ScopeContext,
370    ) {
371    }
372
373    fn validate_use(
374        &self,
375        _key: &Token,
376        _block: &Block,
377        _data: &Everything,
378        _call_key: &Token,
379        _call_block: &Block,
380    ) {
381    }
382
383    fn validate_property_use(
384        &self,
385        _key: &Token,
386        _block: &Block,
387        _property: &Token,
388        _caller: &str,
389        _data: &Everything,
390    ) {
391    }
392
393    fn set_property(&mut self, _key: &Token, _block: &Block, _property: &str) {}
394
395    fn merge_in(&mut self, _other: Box<dyn DbKind>) {}
396}