Skip to main content

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