rush_ecs_core/
display.rs

1//! Rush Core Utilities
2
3use super::blueprint::{Blueprint, ComponentValue, Entity, Region};
4use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Table, *};
5use std::fmt::Display;
6
7// implement Display trait for Blueprint
8impl Display for Blueprint {
9    ///
10    /// Displays the blueprint into human-readable format via a
11    /// comfy CLI table
12    ///
13    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        // get World table
15        let world_table = get_world_table_display(self);
16
17        // get per Region table
18        let mut region_tables: Vec<Table> = Vec::new();
19        for region in self.regions.keys() {
20            let table = get_region_table_display(region, self);
21            region_tables.push(table);
22        }
23
24        // get per Entity table
25        let mut entity_tables: Vec<Table> = Vec::new();
26        for entity in self.entities.keys() {
27            let table = get_entity_table_display(entity, self);
28            entity_tables.push(table);
29        }
30
31        // get Instances table
32        let mut instances_table: Vec<Table> = Vec::new();
33        for region in self.regions.keys() {
34            if let Some(entities_in_region) = self.instances.get(region) {
35                for entity in entities_in_region.keys() {
36                    let table = get_instances_table_display(region, entity, self);
37                    instances_table.push(table);
38                }
39            }
40        }
41
42        // convert to String
43        let region_tables_string = region_tables
44            .iter()
45            .map(|t| format!("{t}"))
46            .collect::<Vec<_>>()
47            .join("\n\n");
48
49        // convert to String
50        let entity_tables_string = entity_tables
51            .iter()
52            .map(|t| format!("{t}"))
53            .collect::<Vec<_>>()
54            .join("\n\n");
55
56        // convert to String
57        let instances_table_string = instances_table
58            .iter()
59            .map(|t| format!("{t}"))
60            .collect::<Vec<_>>()
61            .join("\n\n");
62
63        write!(
64            f,
65            "{world_table}\n\n{region_tables_string}\n\n{entity_tables_string}\n\n{instances_table_string}"
66        )
67    }
68}
69
70/// Get the printable World table from [`Blueprint`]
71///
72/// Constructs the World table from the [`Blueprint`] and
73/// gives a comfy CLI table. Mainly used for Blueprint's
74/// Display trait
75///
76pub fn get_world_table_display(blueprint: &Blueprint) -> Table {
77    // build World table
78    let mut world_table = Table::new();
79
80    // count instances
81    let mut instances_count = 0;
82    for region in blueprint.regions.keys() {
83        for entity in blueprint.entities.keys() {
84            // if no instance in this region, skip region
85            if !blueprint.instances.contains_key(region) {
86                break;
87            }
88
89            // unwrap ok, previously checked
90            let regional = blueprint.instances.get(region).unwrap();
91            // if no instance in this entity, continue
92            if !regional.contains_key(entity) {
93                continue;
94            }
95
96            // unwrap ok, previously checked
97            let instances = regional.get(entity).unwrap();
98            let count = instances.iter().filter(|_| true).count();
99            // update total instances count
100            instances_count += count;
101        }
102    }
103
104    world_table
105        .load_preset(UTF8_FULL)
106        .apply_modifier(UTF8_ROUND_CORNERS)
107        .set_header(vec![
108            Cell::new("World")
109                .fg(Color::Green)
110                .add_attribute(Attribute::Bold),
111            Cell::new(&blueprint.name),
112        ])
113        .add_row(vec![
114            Cell::new("Regions").add_attribute(Attribute::Bold),
115            Cell::new(blueprint.regions.keys().filter(|_| true).count()),
116        ])
117        .add_row(vec![
118            Cell::new("Entities").add_attribute(Attribute::Bold),
119            Cell::new(blueprint.entities.keys().filter(|_| true).count()),
120        ])
121        .add_row(vec![
122            Cell::new("Instances").add_attribute(Attribute::Bold),
123            Cell::new(instances_count),
124        ]);
125
126    world_table
127}
128
129/// Get the printable Region table from [`Blueprint`]
130///
131/// Constructs the Region table from the [`Blueprint`] and
132/// gives a comfy CLI table. Mainly used for Blueprint's
133/// Display trait
134///
135pub fn get_region_table_display(region: &Region, blueprint: &Blueprint) -> Table {
136    // build Region table
137    let mut regional_table = Table::new();
138    regional_table
139        .load_preset(UTF8_FULL)
140        .apply_modifier(UTF8_ROUND_CORNERS)
141        .set_header(vec![
142            Cell::new("Region")
143                .fg(Color::Green)
144                .add_attribute(Attribute::Bold),
145            Cell::new(region),
146        ]);
147
148    if let Some(entities) = blueprint.regions.get(region) {
149        regional_table.add_row(vec![
150            Cell::new("Entities").add_attribute(Attribute::Bold),
151            Cell::new(entities.join(", ")),
152        ]);
153    }
154
155    regional_table
156}
157
158/// Get the printable Entity table from [`Blueprint`]
159///
160/// Constructs the Entity table from the [`Blueprint`] and
161/// gives a comfy CLI table. Mainly used for Blueprint's
162/// Display trait
163///
164pub fn get_entity_table_display(entity: &Entity, blueprint: &Blueprint) -> Table {
165    // build Region table
166    let mut entity_table = Table::new();
167    entity_table
168        .load_preset(UTF8_FULL)
169        .apply_modifier(UTF8_ROUND_CORNERS)
170        .set_header(vec![
171            Cell::new("Entity")
172                .fg(Color::Green)
173                .add_attribute(Attribute::Bold),
174            Cell::new(entity),
175        ]);
176
177    if let Some(component_tree) = blueprint.entities.get(entity) {
178        for (k, v) in component_tree {
179            entity_table.add_row(vec![
180                Cell::new(k).add_attribute(Attribute::Bold),
181                Cell::new(v),
182            ]);
183        }
184    }
185
186    entity_table
187}
188
189/// Get the printable Instances table from [`Blueprint`]
190///
191/// Constructs the Instances table from the [`Blueprint`] and
192/// gives a comfy CLI table. Mainly used for Blueprint's
193/// Display trait
194///
195pub fn get_instances_table_display(
196    region: &Region,
197    entity: &Entity,
198    blueprint: &Blueprint,
199) -> Table {
200    // build Region table
201    let mut instances_table = Table::new();
202    instances_table
203        .load_preset(UTF8_FULL)
204        .apply_modifier(UTF8_ROUND_CORNERS)
205        .set_header(vec![
206            Cell::new("Instances")
207                .fg(Color::Green)
208                .add_attribute(Attribute::Bold),
209            Cell::new(format!("{region}, {entity}")),
210        ]);
211
212    // get instances from Region-Entity pair and construct
213    // instances table from it
214    //
215    // get table for existing Entities in certain Region
216    if let Some(entities_in_region) = blueprint.instances.get(region) {
217        // get instances in certain Entity, in Region
218        if let Some(instances) = entities_in_region.get(entity) {
219            for (idx, instance) in instances.iter().enumerate() {
220                // get all string pairs for combining later
221                let mut instance_string_pairs: Vec<String> = Vec::new();
222                // c_value = ComponentValue
223                for (k, c_value) in instance {
224                    let value_string = match c_value {
225                        ComponentValue::String(v) => v.to_string(), // already a String
226                        ComponentValue::Integer(v) => format!("{v}"),
227                        ComponentValue::Float(v) => format!("{v}"),
228                        ComponentValue::Boolean(v) => format!("{v}"),
229                    };
230                    instance_string_pairs.push(format!("{k} = {value_string}"));
231                }
232                // add row to instances_table
233                instances_table.add_row(vec![
234                    Cell::new(idx).add_attribute(Attribute::Bold),
235                    // combine instance string pairs into 1 long string
236                    Cell::new(instance_string_pairs.join(", ")),
237                ]);
238            }
239        }
240    }
241
242    instances_table
243}
244
245#[cfg(test)]
246mod tests {
247    use crate::blueprint::{Blueprint, ComponentTree, ComponentTypeTree, ComponentValue};
248    use std::collections::btree_map::BTreeMap;
249
250    // TODO: Strip escape codes from string and match
251    #[test]
252    fn test_blueprint_display() {
253        let mut blueprint = Blueprint::new(
254            "Test World".to_string(),
255            "Test World description".to_string(),
256        );
257
258        let region1 = String::from("region1");
259        let region2 = String::from("region2");
260        let entity1 = String::from("entity1");
261        let entity2 = String::from("entity2");
262
263        // load mock regions
264        blueprint.add_region(region1.clone(), vec![entity1.clone()]);
265        blueprint.add_region(region2.clone(), vec![entity2.clone()]);
266        // load mock entity1
267        let mut component_type_tree1: ComponentTypeTree = BTreeMap::new();
268        component_type_tree1.insert("x".to_string(), "int".to_string());
269        component_type_tree1.insert("y".to_string(), "int".to_string());
270        blueprint.add_entity(entity1.clone(), component_type_tree1);
271        // load mock entity2
272        let mut component_type_tree2: ComponentTypeTree = BTreeMap::new();
273        component_type_tree2.insert("w".to_string(), "float".to_string());
274        component_type_tree2.insert("h".to_string(), "float".to_string());
275        blueprint.add_entity(entity2.clone(), component_type_tree2);
276        // load mock instances1
277        let mut component_tree1: ComponentTree = BTreeMap::new();
278        component_tree1.insert("x".to_string(), ComponentValue::Integer(143));
279        component_tree1.insert("y".to_string(), ComponentValue::Integer(143));
280        blueprint
281            .add_instance(region1.clone(), entity1.clone(), component_tree1)
282            .unwrap();
283        // load mock instances2
284        let mut component_tree2: ComponentTree = BTreeMap::new();
285        component_tree2.insert("w".to_string(), ComponentValue::Float(143.0));
286        component_tree2.insert("h".to_string(), ComponentValue::Float(143.0));
287        blueprint
288            .add_instance(region2, entity2, component_tree2)
289            .unwrap();
290
291        println!("{blueprint}");
292    }
293}