From c6ae9b71cd74a580164f840f72a278d8124929f9 Mon Sep 17 00:00:00 2001 From: vandomej Date: Sat, 6 Sep 2025 11:20:25 -0700 Subject: [PATCH] Cleaning up code and adding more documentation --- file_linked/src/constants/data_format.rs | 6 ++ file_linked/src/error.rs | 10 +++ file_linked/src/lib.rs | 106 ++++++++++++++++++++++- gemla/src/bin/bin.rs | 2 +- gemla/src/core/genetic_node.rs | 23 ++--- 5 files changed, 134 insertions(+), 13 deletions(-) diff --git a/file_linked/src/constants/data_format.rs b/file_linked/src/constants/data_format.rs index 9a9940e..b0f82b8 100644 --- a/file_linked/src/constants/data_format.rs +++ b/file_linked/src/constants/data_format.rs @@ -1,5 +1,11 @@ +//! Constants used in the `file_linked` crate. +//! This module defines enums and constants that are used throughout the crate, such as data formats for serialization. + +/// Data formats supported for serialization and deserialization. #[derive(Debug)] pub enum DataFormat { + /// Use the `bincode` format for serialization. Bincode, + /// Use the `json` format for serialization. Json, } diff --git a/file_linked/src/error.rs b/file_linked/src/error.rs index 44b7972..cbd09e3 100644 --- a/file_linked/src/error.rs +++ b/file_linked/src/error.rs @@ -1,11 +1,21 @@ +//! Error types for the `file_linked` crate. +//! This module defines a unified error type for the crate, wrapping errors from dependencies and providing conversions +//! from common error types. + use thiserror::Error; +/// The main error type for the `file_linked` crate. #[derive(Error, Debug)] pub enum Error { + /// An error originating from the `bincode` crate. #[error(transparent)] Serialization(bincode::Error), + + /// An IO error, such as file not found or permission denied. #[error(transparent)] IO(std::io::Error), + + /// Any other error, wrapped using `anyhow`. #[error(transparent)] Other(#[from] anyhow::Error), } diff --git a/file_linked/src/lib.rs b/file_linked/src/lib.rs index 694b114..30429a5 100644 --- a/file_linked/src/lib.rs +++ b/file_linked/src/lib.rs @@ -1,5 +1,61 @@ -//! A wrapper around an object that ties it to a physical file +//! # file_linked +//! +//! **file_linked** is a Rust library for binding objects directly to files, providing persistent, file-backed state management for any serializable type. It is designed for use cases where you want to transparently synchronize in-memory data with a file, supporting both synchronous and asynchronous mutation patterns. +//! +//! ## Features +//! - Generic wrapper [`FileLinked`] for any serializable type +//! - Automatic file synchronization on mutation or replacement +//! - Asynchronous and thread-safe access using `tokio::RwLock` +//! - Support for multiple serialization formats (JSON, Bincode) +//! - Simple API for reading, mutating, and replacing data +//! - Error handling with unified [`Error`] type +//! +//! ## Modules +//! - [`constants`]: Data format selection and related constants +//! - [`error`]: Error types for serialization, I/O, and general errors +//! +//! ## Example +//! ```rust,ignore +//! # use file_linked::*; +//! # use file_linked::constants::data_format::DataFormat; +//! # use serde::{Deserialize, Serialize}; +//! # use std::path::PathBuf; +//! # use tokio; +//! +//! #[derive(Deserialize, Serialize)] +//! struct Test { +//! pub a: u32, +//! pub b: String, +//! pub c: f64 +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! let test = Test { a: 1, b: String::from("two"), c: 3.0 }; +//! let mut linked = FileLinked::new(test, &PathBuf::from("./file"), DataFormat::Json).await.unwrap(); +//! linked.mutate(|x| x.a += 1).await.unwrap(); +//! assert_eq!(linked.readonly().read().await.a, 2); +//! } +//! ``` +//! +//! ## Usage +//! 1. Create a serializable struct and instantiate a [`FileLinked`] object. +//! 2. Use `.readonly()` for shared access, `.mutate()` or `.replace()` for mutation (which automatically syncs to file). +//! 3. Use `.from_file()` to restore from disk. +//! +//! ## Crate Organization +//! - All core logic is in [`FileLinked`]. +//! - Data format selection is in [`constants`]. +//! - Error types and helpers are in [`error`]. +//! +//! [`FileLinked`]: crate::FileLinked +//! [`Error`]: crate::error::Error +//! [`constants`]: crate::constants +//! [`error`]: crate::error +#![warn(missing_docs)] + +/// Constants used in the `file_linked` crate. pub mod constants; pub mod error; @@ -236,12 +292,14 @@ where /// let mut linked_test = FileLinked::new(test, &PathBuf::from("./temp"), DataFormat::Bincode).await /// .expect("Unable to create file linked object"); /// + /// // Scope to limit the lifetime of the read lock /// { /// let readonly = linked_test.readonly(); /// let readonly_ref = readonly.read().await; /// assert_eq!(readonly_ref.a, 1); /// } /// + /// // After mutate closure completes the object will be written to a file /// linked_test.mutate(|t| t.a = 2).await?; /// /// let readonly = linked_test.readonly(); @@ -332,6 +390,52 @@ where T: Serialize + DeserializeOwned + Send + 'static, { /// Asynchronously modifies the data contained in a `FileLinked` object using an async callback `op`. + /// The callback `op` is given an `Arc>` to the underlying data, allowing for concurrent access patterns. + /// After the async operation completes the data is written to a file to synchronize the state. + /// + /// # Examples + /// ```rust + /// # use file_linked::*; + /// # use file_linked::error::Error; + /// # use file_linked::constants::data_format::DataFormat; + /// # use serde::{Deserialize, Serialize}; + /// # use std::path::PathBuf; + /// # use tokio; + /// # + /// # #[derive(Deserialize, Serialize)] + /// # struct Test { + /// # pub a: u32, + /// # pub b: String, + /// # pub c: f64 + /// # } + /// # #[tokio::main] + /// # async fn main() -> Result<(), Error> { + /// let test = Test { + /// a: 1, + /// b: String::from(""), + /// c: 0.0 + /// }; + /// let mut linked_test = FileLinked::new(test, &PathBuf::from("./temp"), DataFormat::Bincode).await + /// .expect("Unable to create file linked object"); + /// + /// // Scope to limit the lifetime of the read lock + /// { + /// let readonly = linked_test.readonly(); + /// let readonly_ref = readonly.read().await; + /// assert_eq!(readonly_ref.a, 1); + /// } + /// + /// // After mutate_async closure completes the object will be written to a file + /// linked_test.mutate_async(|t| async move { t.write().await.a = 2 }).await?; + /// + /// let readonly = linked_test.readonly(); + /// let readonly_ref = readonly.read().await; + /// assert_eq!(readonly_ref.a, 2); + /// # drop(linked_test); + /// # std::fs::remove_file("./temp").expect("Unable to remove file"); + /// # Ok(()) + /// # } + /// ``` pub async fn mutate_async(&mut self, op: F) -> Result where F: FnOnce(Arc>) -> Fut, diff --git a/gemla/src/bin/bin.rs b/gemla/src/bin/bin.rs index 4e84f51..8cfd0d3 100644 --- a/gemla/src/bin/bin.rs +++ b/gemla/src/bin/bin.rs @@ -7,13 +7,13 @@ mod test_state; use anyhow::Result; use clap::Parser; -use test_state::TestState; use file_linked::constants::data_format::DataFormat; use gemla::{ core::{Gemla, GemlaConfig}, error::log_error, }; use std::{path::PathBuf, time::Instant}; +use test_state::TestState; // const NUM_THREADS: usize = 2; diff --git a/gemla/src/core/genetic_node.rs b/gemla/src/core/genetic_node.rs index 657e9f9..409d4a6 100644 --- a/gemla/src/core/genetic_node.rs +++ b/gemla/src/core/genetic_node.rs @@ -62,13 +62,13 @@ pub struct GeneticNodeContext { /// // ... /// } /// ``` -/// +/// /// [`Gemla`]: crate::Gemla #[async_trait] pub trait GeneticNode: Send { /// Custom type that provides a shared context across different nodes and simulations. Useful if you want to manage /// conncurrency or share data between nodes. - /// + /// /// # Example /// ```ignore /// pub struct SharedContext { @@ -89,7 +89,7 @@ pub trait GeneticNode: Send { /// * A boxed instance of the initialized node. /// /// # Example - /// ```rust + /// ```rust,ignore /// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext}; /// # use async_trait::async_trait; /// # use serde::{Serialize, Deserialize}; @@ -100,7 +100,7 @@ pub trait GeneticNode: Send { /// population: Vec, /// max_generations: u64, /// } - /// + /// /// #[async_trait] /// impl GeneticNode for TestState { /// type Context = (); @@ -125,7 +125,7 @@ pub trait GeneticNode: Send { /// * `Ok(true)` if the node should continue to the next phase, `Ok(false)` if finished. /// /// # Example - /// ```rust + /// ```rust,ignore /// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext}; /// # use async_trait::async_trait; /// # use serde::{Serialize, Deserialize}; @@ -136,7 +136,7 @@ pub trait GeneticNode: Send { /// population: Vec, /// max_generations: u64, /// } - /// + /// /// #[async_trait] /// impl GeneticNode for TestState { /// type Context = (); @@ -152,7 +152,8 @@ pub trait GeneticNode: Send { /// // ... /// } /// ``` - async fn simulate(&mut self, context: GeneticNodeContext) -> Result; + async fn simulate(&mut self, context: GeneticNodeContext) + -> Result; /// Mutates members in a population and/or crossbreeds them to produce new offspring. /// @@ -164,7 +165,7 @@ pub trait GeneticNode: Send { /// * `Err(Error)` if an error occurred during mutation. /// /// # Example - /// ```rust + /// ```rust,ignore /// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext}; /// # use async_trait::async_trait; /// # use serde::{Serialize, Deserialize}; @@ -175,7 +176,7 @@ pub trait GeneticNode: Send { /// population: Vec, /// max_generations: u64, /// } - /// + /// /// #[async_trait] /// impl GeneticNode for TestState { /// type Context = (); @@ -213,7 +214,7 @@ pub trait GeneticNode: Send { /// * `context` - The context for merging. /// /// # Example - /// ```rust + /// ```rust,ignore /// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext}; /// # use async_trait::async_trait; /// # use serde::{Serialize, Deserialize}; @@ -223,7 +224,7 @@ pub trait GeneticNode: Send { /// population: Vec, /// max_generations: u64, /// } - /// + /// /// #[async_trait] /// impl GeneticNode for TestState { /// type Context = ();