From fd3d94ac3657ebbbd970df374cfc6787fe74783a Mon Sep 17 00:00:00 2001 From: TCA166 Date: Tue, 30 Apr 2024 17:55:59 +0200 Subject: [PATCH] Bugfixes and documentation improvements --- Makefile | 6 +++- src/game_object.rs | 61 +++++++++++++++++++++++++++------------- src/game_state.rs | 3 +- src/main.rs | 6 +++- src/save_file.rs | 53 ++++++++++++++++++++++++++++++---- src/structures/memory.rs | 32 ++++++++++----------- src/structures/mod.rs | 26 +++++++++++++---- 7 files changed, 138 insertions(+), 49 deletions(-) diff --git a/Makefile b/Makefile index 3351037..db547bd 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,12 @@ check: src/*.rs src/structures/*.rs doc: src/*.rs src/structures/*.rs @echo "Building documentation..." - cargo doc --no-deps --document-private-items + cargo doc --no-deps --document-private-items --bin ck3_history_extractor dependencies: @echo "Installing dependencies..." sudo dnf install rust cargo rustup rust-src + +clean: + @echo "Cleaning up..." + cargo clean diff --git a/src/game_object.rs b/src/game_object.rs index 4005f86..13dff4f 100644 --- a/src/game_object.rs +++ b/src/game_object.rs @@ -2,9 +2,8 @@ use std::{cell::Ref, collections::{hash_map, HashMap}, slice}; use crate::structures::Shared; -/// A value that can be stored in a SaveFile and is held by a GameObject -/// -/// This is a wrapper around a String or a GameObject +/// A value that can be stored in a SaveFile and is held by a GameObject. +/// This is a wrapper around a String or a GameObject. #[derive(Debug)] pub enum SaveFileValue{ String(Shared), @@ -13,9 +12,14 @@ pub enum SaveFileValue{ impl SaveFileValue { - /// Get the value as a string reference + /// Get the value as a string reference. + /// Mainly used for convenience. /// - /// ## Returns + /// # Panics + /// + /// If the value is not a string + /// + /// # Returns /// /// A reference to the string pub fn as_string_ref(&self) -> Option>{ @@ -27,11 +31,11 @@ impl SaveFileValue { /// Get the value as a string /// - /// ## Panics + /// # Panics /// /// Panics if the value is not a string /// - /// ## Returns + /// # Returns /// /// A reference to the string pub fn as_string(&self) -> Shared{ @@ -43,7 +47,11 @@ impl SaveFileValue { /// Get the value as a GameObject reference /// - /// ## Returns + /// # Panics + /// + /// Panics if the value is not a GameObject + /// + /// # Returns /// /// A reference to the GameObject pub fn as_object_ref(&self) -> Option>{ @@ -55,9 +63,12 @@ impl SaveFileValue { } -/// Representation of a save file object -/// -/// Acts like a named dictionary and array, may be either or both +/// Representation of a save file object. +/// These are the main data structure used to store game data. +/// Each belongs to a section, but that is not stored here. +/// Acts like a named dictionary and array, may be either or both or neither. +/// Each has a name, which isn't unique. +/// Holds [SaveFileValue]s, which are either strings or other GameObjects. #[derive(Debug)] pub struct GameObject{ inner: HashMap, @@ -76,7 +87,7 @@ impl GameObject{ } } - /// Create a new GameObject + /// Create a new empty GameObject pub fn new() -> GameObject{ GameObject{ inner: HashMap::new(), @@ -90,7 +101,7 @@ impl GameObject{ self.name = name; } - /// Insert a new key value pair into the GameObject + /// Insert a new key value pair into the GameObject dictionary pub fn insert(&mut self, key: String, value: SaveFileValue){ self.inner.insert(key, value); } @@ -100,12 +111,24 @@ impl GameObject{ self.inner.get(key) } - /// Get the value of a key as a string + /// Get the value of a key as a string. + /// Mainly used for convenience. + /// + /// # Panics + /// + /// If the key is missing or the value is not a string + /// pub fn get_string_ref(&self, key: &str) -> Ref<'_, String>{ self.inner.get(key).unwrap().as_string_ref().unwrap() } - /// Get the value of a key as a GameObject + /// Get the value of a key as a GameObject. + /// Mainly used for convenience. + /// + /// # Panics + /// + /// If the key is missing or the value is not a GameObject + /// pub fn get_object_ref(&self, key: &str) -> Ref<'_, GameObject>{ self.inner.get(key).unwrap().as_object_ref().unwrap() } @@ -126,22 +149,22 @@ impl GameObject{ self.array.push(value); } - /// Get the length of the GameObject array + /// Checks if the dictionary and array are empty pub fn is_empty(&self) -> bool{ self.inner.is_empty() && self.array.is_empty() } - /// Get the length of the GameObject array + /// Gets the iterator for the underlying array pub fn get_array_iter(&self) -> slice::Iter{ self.array.iter() } - /// Get the length of the GameObject array + /// Gets the iterator for the underlying dictionary pub fn get_obj_iter(&self) -> hash_map::Iter{ self.inner.iter() } - /// Get the keys of the GameObject + /// Get the keys of the GameObject dictionary pub fn get_keys(&self) -> Vec{ self.inner.keys().map(|x| x.clone()).collect() } diff --git a/src/game_state.rs b/src/game_state.rs index 4eab3cf..23657b8 100644 --- a/src/game_state.rs +++ b/src/game_state.rs @@ -6,7 +6,6 @@ use crate::structures::{Character, Culture, Dynasty, Faith, GameObjectDerived, M use crate::game_object::GameObject; /// A struct representing all known game objects -/// /// It is guaranteed to always return a reference to the same object for the same key. /// Naturally the value of that reference may change as values are added to the game state. pub struct GameState{ @@ -35,10 +34,12 @@ impl GameState{ } } + /// Add a lookup table for traits pub fn add_lookup(&mut self, array:Vec>){ self.traits_lookup = array; } + /// Get a trait by id pub fn get_trait(&self, id:u32) -> Shared{ self.traits_lookup[id as usize].clone() } diff --git a/src/main.rs b/src/main.rs index e4cda9b..df3c7a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -104,7 +104,11 @@ fn main() { let o = i.to_object().unwrap(); let database = o.get_object_ref("database"); for d in database.get_obj_iter(){ - game_state.add_memory(d.1.as_object_ref().unwrap()); + let mem = d.1.as_object_ref(); + if mem.is_none() { + continue; + } + game_state.add_memory(mem.unwrap()); } } "played_character" => { diff --git a/src/save_file.rs b/src/save_file.rs index b4d1359..0a45557 100644 --- a/src/save_file.rs +++ b/src/save_file.rs @@ -6,7 +6,7 @@ use crate::game_object::{GameObject, SaveFileValue}; /// A function that reads a single character from a file /// -/// ## Returns +/// # Returns /// /// The character read or None if the end of the file is reached fn fgetc(file: &mut File) -> Option{ // Shoutout to my C literate homies out there @@ -18,12 +18,30 @@ fn fgetc(file: &mut File) -> Option{ // Shoutout to my C literate homies o return Some(buffer[0] as char); } + +/// A struct that represents a section in a ck3 save file +/// Each section has a name, and holds a file handle to the section +/// +/// # Validity +/// +/// The section is guaranteed to be valid when you get it. +/// However once you call [Section::to_object] or [Section::skip] the section becomes invalid. +/// Trying to do anything with an invalid section will panic. +/// +/// # Example +/// +/// ``` +/// let save = SaveFile::new("save.ck3"); +/// let section = save.next(); +/// let object = section.to_object().unwrap(); +/// ``` pub struct Section{ name: String, file:Option } impl Section{ + /// Create a new section fn new(name: &str, file: File) -> Section{ Section{ name: name.to_string(), @@ -36,11 +54,19 @@ impl Section{ &self.name } + /// Invalidate the section fn invalidate(&mut self){ self.file = None; } - /// Convert the section to a GameObject and invalidate it + /// Convert the section to a GameObject and invalidate it. + /// This is a rather costly process as it has to read the entire section contents and parse them. + /// You can then make a choice if you want to parse the object or [Section::skip] it. + /// The section must be valid. + /// + /// # Panics + /// + /// If the section is invalid pub fn to_object(&mut self) -> Option{ if self.file.is_none(){ panic!("Invalid section"); @@ -150,7 +176,13 @@ impl Section{ return Some(object); } - /// Skip the current section and invalidate it + /// Skip the current section and invalidate it. + /// This is a rather cheap operation as it only reads the file until the end of the section. + /// The section must be valid. + /// + /// # Panics + /// + /// If the section is invalid pub fn skip(&mut self){ if self.file.is_none(){ panic!("Invalid section"); @@ -180,14 +212,24 @@ impl Section{ } } -/// A struct that represents a ck3 save file +/// A struct that represents a ck3 save file. +/// This struct is an iterator that returns sections from the save file. +/// +/// # Example +/// +/// ``` +/// let save = SaveFile::new("save.ck3"); +/// for section in save{ +/// println!("Section: {}", section.get_name()); +/// } pub struct SaveFile{ file: File } impl SaveFile{ - /// Create a new SaveFile instance + /// Create a new SaveFile instance. + /// The filename must be valid of course. pub fn new(filename: &str) -> SaveFile{ SaveFile{ file: File::open(filename).unwrap(), @@ -201,6 +243,7 @@ impl Iterator for SaveFile{ type Item = Section; /// Get the next object in the save file + /// If the file pointer has reached the end of the file then it will return None. fn next(&mut self) -> Option
{ let mut key = String::new(); let file = &mut self.file; diff --git a/src/structures/memory.rs b/src/structures/memory.rs index e574cda..3d79e4f 100644 --- a/src/structures/memory.rs +++ b/src/structures/memory.rs @@ -4,23 +4,28 @@ use serde::Serialize; use serde::ser::SerializeStruct; use super::{Character, GameObjectDerived, Shared}; use crate::game_object::GameObject; +use crate::game_state::GameState; pub struct Memory { pub id: u32, pub date: Shared, pub r#type: Shared, - pub participants: Vec<(Shared, Shared)>, + pub participants: Vec<(String, Shared)>, +} + +fn get_participants(participants:&mut Vec<(String, Shared)>, base:&Ref<'_, GameObject>, game_state:&mut GameState){ + let participants_node = base.get("participants"); + if participants_node.is_some(){ + for part in participants_node.unwrap().as_object_ref().unwrap().get_obj_iter(){ + participants.push((part.0.clone(), game_state.get_character(part.1.as_string().borrow().as_str()).clone())); + } + } } impl GameObjectDerived for Memory { - fn from_game_object(base: Ref<'_, GameObject>, game_state: &mut crate::game_state::GameState) -> Self { - let part = base.get("participants").unwrap().as_object_ref().unwrap(); //FIXME sometimes missing? + fn from_game_object(base: Ref<'_, GameObject>, game_state: &mut GameState) -> Self { let mut participants = Vec::new(); - for k in part.get_keys(){ - let v = part.get(&k).unwrap(); - participants.push((Rc::from(RefCell::from(k)), game_state.get_character(v.as_string_ref().unwrap().as_str()).clone())); - } - println!("Memory: {:?}", base); + get_participants(&mut participants, &base, game_state); Memory{ date: base.get("creation_date").unwrap().as_string(), r#type: base.get("type").unwrap().as_string(), @@ -38,17 +43,10 @@ impl GameObjectDerived for Memory { } } - fn init(&mut self, base: Ref<'_, GameObject>, game_state: &mut crate::game_state::GameState) { - let part = base.get("participants").unwrap().as_object_ref().unwrap(); - let mut participants = Vec::new(); - for k in part.get_keys(){ - let v = part.get(&k).unwrap(); - participants.push((Rc::from(RefCell::from(k)), game_state.get_character(v.as_string_ref().unwrap().as_str()).clone())); - } + fn init(&mut self, base: Ref<'_, GameObject>, game_state: &mut GameState) { self.date = base.get("date").unwrap().as_string(); self.r#type = base.get("type").unwrap().as_string(); - self.participants = participants; - self.id = base.get_name().parse::().unwrap(); + get_participants(&mut self.participants, &base, game_state); } fn get_id(&self) -> u32 { diff --git a/src/structures/mod.rs b/src/structures/mod.rs index 4b3a368..4da98f7 100644 --- a/src/structures/mod.rs +++ b/src/structures/mod.rs @@ -27,20 +27,36 @@ pub use memory::Memory; mod title; pub use title::Title; -/// A type alias for shared objects +/// A type alias for shared objects. +/// Aliases: [std::rc::Rc]<[std::cell::RefCell]<>> +/// +/// # Example +/// +/// ``` +/// let obj:Shared = Rc::new(RefCell::new("Hello")); +/// +/// let value:Ref = obj.borrow(); +/// ``` pub type Shared = std::rc::Rc>; -/// A trait for objects that can be created from a GameObject +/// A trait for objects that can be created from a [GameObject]. +/// Currently these include: [Character], [Culture], [Dynasty], [Faith], [Memory], [Player], [Title]. +/// The idea is to have uniform interface for the object initialization. pub trait GameObjectDerived{ - /// Create a new object from a GameObject and auxiliary data from the game state + /// Create a new object from a GameObject and auxiliary data from the game state. fn from_game_object(base:Ref<'_, GameObject>, game_state:&mut GameState) -> Self; /// Create a dummy object that can be used as a placeholder + /// Can be used to initialize an object from a section yet to be parsed. fn dummy(id:u32) -> Self; - /// Initialize the object (ideally dummy) with auxiliary data from the game state + /// Initialize the object (ideally dummy) with auxiliary data from the game state. + /// This can be called multiple times, but why would you do that? fn init(&mut self, base:Ref<'_, GameObject>, game_state:&mut GameState); - /// Get the id of the object + /// Get the id of the object. + /// All CK3 objects have an id that is a number. + /// Within a given section that number is unique. + /// For example, all characters have a unique id, but a title and a character can have the same id. fn get_id(&self) -> u32; }