# Mize Part System (LLM generated guide) --- ## Core Concepts ### The MizePart Trait All parts implement the `MizePart` trait, which provides: ```rust pub trait MizePart: MizePartGenerated { fn name(&self) -> &'static str; // Part identifier fn get_mize(&mut self) -> &mut Mize; // Access to the Mize instance fn init(&mut self, mize: &mut Mize) -> MizeResult<()>; // Initialization fn run(&mut self, mize: &mut Mize) -> MizeResult<()>; // Execution fn deps(&self) -> &'static [&'static str]; // Dependencies fn opts(&self, mize: &mut Mize); // Config options // ... async variants } ``` ### Part Registration Parts register themselves with the Mize instance using: - **`mize.register_part(Box::new(MyPart { ... }))`** - For parts with state and methods - **`mize.add_name_only_part("name")`** - For simple parts that only need to be known by name (and have no state) ### Part Access Parts can access other parts using: ```rust // Get a part by name (returns a guard that ensures the part is returned) let mut part = mize.get_part("part_name")?; // Get a typed part (for parts with structs) let mut typed_part = mize.get_part_native::("other_part")?; ``` **Important**: Users must ensure that dependent parts are enabled before the parts that depend on them. There's no automatic dependency resolution. --- ## Current Implementation (Compiled-in Parts) Currently, all parts are compiled into the `marts` crate and registered via the `compile_in_parts!` macro in the application's `main.rs`. ### Example: `apps/os/src/main.rs` ```rust compile_in_parts! { mize, "cli": marts::cli, "auth": marts::part_client_auth, "spacetime": marts::spacetime, "ppc.server": marts::ppc::server::part_ppc_server, "c2vi": marts::part_c2vi, "sci": marts::sci::part_sci, "sci.timetrack": marts::sci::timetrack::part_timetrack, "sci.inbox": marts::sci::inbox::part_sci_inbox, }; ``` Each entry maps a part name to a function that registers the part with the Mize instance. ### How It Works 1. The macro reads the `enable` config option (a list of part names) 2. For each enabled part, it calls the corresponding registration function 3. Each registration function creates the part and calls `mize.register_part()` --- ## Creating a New Part ### Option 1: Simple Name-Only Part For parts that don't need state or methods: ```rust // In your part module pub fn part_my_simple_part(mize: &mut Mize) -> MizeResult<()> { // Just register the name mize.add_name_only_part("my_simple_part"); Ok(()) } ``` ### Option 2: Part with State and Methods (Using `mize_part` Macro) For parts that need to store state and expose methods: ```rust use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult}; #[mize_part] // or #[mize_part("custom_name")] for custom name #[derive(Default)] pub struct MyPart { mize: Mize, // Required field counter: u32, // Your custom state api_key: String, // More state } // Implement MizePartCreate for parts that need custom initialization impl MizePartCreate for MyPart { // Optional: override create_generated for custom initialization } impl MizePart for MyPart { // Optional: override name if not using the struct name fn name(&self) -> &'static str { "my_part" // Default would be "mypart" from struct name } // Optional: define config options this part needs fn opts(&self, mize: &mut Mize) { mize.new_opt("my_part.api_key"); mize.new_opt("my_part.counter_initial"); } // Optional: initialization logic fn init(&mut self, mize: &mut Mize) -> MizeResult<()> { self.counter = mize.get_config("my_part.counter_initial")?.value_u32()?; self.api_key = mize.get_config("my_part.api_key")?.value_string()?; Ok(()) } // Optional: define dependencies on other parts fn deps(&self) -> &'static [&'static str] { &["auth"] // This part requires "auth" to be loaded first } } // The registration function pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> { mize.register_part(Box::new(MyPart { mize: mize.clone(), counter: 0, api_key: String::new(), }))?; Ok(()) } // You can add methods to your part struct impl MyPart { pub fn increment(&mut self) -> u32 { self.counter += 1; self.counter } pub fn get_api_key(&self) -> &str { &self.api_key } } ``` ### Using the Part from Another Part ```rust pub fn part_another_part(mize: &mut Mize) -> MizeResult<()> { // Get the MyPart instance let mut my_part = mize.get_part_native::("my_part")?; // Call its methods let count = my_part.increment(); let api_key = my_part.get_api_key(); // ... your logic Ok(()) } ``` --- ## The `mize_part` Macro The `#[mize_part]` procedural macro simplifies part creation by automatically implementing the required traits. ### What It Generates For a struct like: ```rust #[mize_part] #[derive(Default)] pub struct MyPart { mize: Mize, // ... your fields } ``` The macro generates: 1. **`MizePartGenerated` implementation** - Provides default implementations for the generated trait methods 2. **`MizePartCreateGenerated` implementation** - Provides a default `create_generated` method 3. **`MizePartCreateThisSucks` implementation** - Compatibility trait 4. **Extern "C" function** - Named `make_mize_part_` for dynamic loading ### Custom Part Name ```rust #[mize_part("custom_name")] pub struct MyPart { mize: Mize, } ``` This will use "custom_name" instead of the derived "mypart". ### Requirements - The struct **must** have a field named `mize: Mize` - The struct should derive `Default` or implement it --- ## Configuration ### Defining Config Options (about to be deprecad, does nothing) Parts declare what config options they need in their `opts()` method: ```rust impl MizePart for MyPart { fn opts(&self, mize: &mut Mize) { mize.new_opt("my_part.api_url"); mize.new_opt("my_part.timeout"); } } ``` ### Using Config Options Parts retrieve config using `mize.get_config()`: ```rust fn init(&mut self, mize: &mut Mize) -> MizeResult<()> { let api_url = mize.get_config("my_part.api_url")?.value_string()?; let timeout = mize.get_config("my_part.timeout")?.value_u64()?; Ok(()) } ``` ### User Configuration Users configure parts via TOML files or environment variables. Example `config.toml`: ```toml [my_part] api_url = "https://api.example.com" timeout = 30 [enable] # List of parts to enable my_part = true auth = true spacetime = true ``` Or via command line: ```bash mize --opt my_part.api_url=https://api.example.com run ``` --- ## Part Dependencies ### Declaring Dependencies (about to be deprecad, does nothing) Parts declare their dependencies in the `deps()` method: ```rust impl MizePart for MyPart { fn deps(&self) -> &'static [&'static str] { &["auth", "spacetime"] } } ``` **Note**: This is currently informational only. Users must manually ensure dependencies are enabled. ### Accessing Dependencies Use `get_part_native()` to access other parts: ```rust fn run(&mut self, mize: &mut Mize) -> MizeResult<()> { // Get the auth part let mut auth = mize.get_part_native::("client_auth")?; // Use it let token = auth.id_token()?; // Get spacetime let mut spacetime = mize.get_part_native::("spacetime")?; Ok(()) } ``` --- ## Future Vision: Separate Crates The goal is to move each part to its own crate following the naming convention **`mize-part-`**. when creating a part make a new Cargo project like this already. ### Structure of a Part Crate ``` mize-part-discord/ ├── Cargo.toml └── src/ └── lib.rs ``` ### Example: `mize-part-discord/Cargo.toml` ```toml [package] name = "mize-part-discord" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib", "rlib"] [dependencies] mize = { path = "../mize" } serenity = "0.12" ``` ### Example: `mize-part-discord/src/lib.rs` ```rust use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult}; use serenity::Client; #[mize_part] #[derive(Default)] pub struct DiscordPart { mize: Mize, client: Option, } impl MizePartCreate for DiscordPart {} impl MizePart for DiscordPart { fn name(&self) -> &'static str { "discord" } fn opts(&self, mize: &mut Mize) { mize.new_opt("discord.token"); } fn init(&mut self, mize: &mut Mize) -> MizeResult<()> { let token = mize.get_config("discord.token")?.value_string()?; // Initialize serenity client... Ok(()) } } // The registration function - THIS IS WHAT MATTERS pub fn part_discord(mize: &mut Mize) -> MizeResult<()> { mize.register_part(Box::new(DiscordPart { mize: mize.clone(), client: None, }))?; Ok(()) } ``` ### Using Separate Part Crates In your application's `Cargo.toml`: ```toml [dependencies] mize = { path = "../mize" } mize-part-discord = { path = "../mize-part-discord" } mize-part-auth = { path = "../mize-part-auth" } ``` In your application's `main.rs`: ```rust compile_in_parts! { mize, "discord": mize_part_discord::part_discord, "auth": mize_part_auth::part_auth, }; ``` ## Complete Example: Creating a New Part Let's create a complete example of a new part called `mize-part-greeting`. ### Step 1: Create the Crate ```bash cargo new --lib mize-part-greeting cd mize-part-greeting ``` ### Step 2: Edit `Cargo.toml` ```toml [package] name = "mize-part-greeting" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib", "rlib"] [dependencies] mize = { path = "../mize" } ``` ### Step 3: Create the Part (`src/lib.rs`) ```rust use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult}; /// A simple greeting part that stores a name and can generate greetings #[mize_part] #[derive(Default)] pub struct GreetingPart { mize: Mize, name: String, } impl MizePartCreate for GreetingPart {} impl MizePart for GreetingPart { fn name(&self) -> &'static str { "greeting" } fn opts(&self, mize: &mut Mize) { mize.new_opt("greeting.name"); mize.new_opt("greeting.formal"); } fn init(&mut self, mize: &mut Mize) -> MizeResult<()> { self.name = mize .get_config("greeting.name")? .value_string() .unwrap_or_else(|_| "World".to_string()); Ok(()) } } impl GreetingPart { /// Generate a greeting message pub fn greet(&self) -> String { let formal = self .mize .get_config("greeting.formal") .map(|v| v.value_bool().unwrap_or(false)) .unwrap_or(false); if formal { format!("Good day, {}!", self.name) } else { format!("Hello, {}!", self.name) } } /// Set the name pub fn set_name(&mut self, name: String) { self.name = name; } /// Get the current name pub fn get_name(&self) -> &str { &self.name } } /// Registration function - this is what the compile_in_parts! macro will call pub fn part_greeting(mize: &mut Mize) -> MizeResult<()> { mize.register_part(Box::new(GreetingPart { mize: mize.clone(), name: String::new(), }))?; Ok(()) } ``` ### Step 4: Add to Your Application In your application's `Cargo.toml`: ```toml [dependencies] mize = { path = "../mize" } mize-part-greeting = { path = "../mize-part-greeting" } ``` In your application's `main.rs`: ```rust compile_in_parts! { mize, "greeting": mize_part_greeting::part_greeting, }; ``` ### Step 5: Use the Part ```rust // In another part or in your CLI let mut greeting = mize.get_part_native::("greeting")?; println!("{}", greeting.greet()); // Change the name greeting.set_name("Alice".to_string()); println!("{}", greeting.greet()); ``` ### Step 6: Configure It Create a `config.toml`: ```toml [greeting] name = "Bob" formal = true [enable] greeting = true ``` Run with: ```bash mize --config config.toml run ``` --- ## Best Practices 1. **Naming**: Use `mize-part-` for crate names and `part_` for registration functions 2. **State**: Store part-specific state in the part struct 3. **Dependencies**: Declare dependencies in `deps()` and document them 4. **Config**: Declare all config options in `opts()` 5. **Error Handling**: Use `MizeResult` and `mize_err!` for errors 6. **Documentation**: Document your part's purpose, config options, and methods --- ## Summary - Parts are modular units of functionality in Mize - Each part implements the `MizePart` trait - Use `#[mize_part]` macro for parts with state and methods - Register parts with `mize.register_part()` or `mize.add_name_only_part()` - Parts access config via `mize.get_config()` - Parts access other parts via `mize.get_part_native::()` - Users enable parts via the `enable` config option - Future: Each part will be its own crate named `mize-part-` --- ## Quick Reference ### Common Patterns **Register a part:** ```rust mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?; ``` **Get a part:** ```rust let mut part = mize.get_part_native::("my_part")?; ``` **Get config:** ```rust let value = mize.get_config("my_part.setting")?.value_string()?; ``` **Declare config option:** ```rust mize.new_opt("my_part.setting"); ``` **Define a part struct:** ```rust #[mize_part] #[derive(Default)] pub struct MyPart { mize: Mize, // your fields... } ``` **Registration function:** ```rust pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> { mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?; Ok(()) } ```