14 KiB
Mize Part System (LLM generated guide)
Overview
Mize is built around a parts architecture that allows you to compose functionality modularly. Each part is a self-contained unit that can:
- Store and manage its own state
- Expose methods to other parts
- Register itself with the Mize instance
- Define configuration options
Parts are the primary way to extend Mize's functionality. The system is designed so that parts can depend on each other, and users can enable/disable parts as needed.
Core Concepts
The MizePart Trait
All parts implement the MizePart trait, which provides:
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 methodsmize.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:
// 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::<OtherPart>("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
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
- The macro reads the
enableconfig option (a list of part names) - For each enabled part, it calls the corresponding registration function
- 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:
// 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:
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
pub fn part_another_part(mize: &mut Mize) -> MizeResult<()> {
// Get the MyPart instance
let mut my_part = mize.get_part_native::<MyPart>("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:
#[mize_part]
#[derive(Default)]
pub struct MyPart {
mize: Mize,
// ... your fields
}
The macro generates:
MizePartGeneratedimplementation - Provides default implementations for the generated trait methodsMizePartCreateGeneratedimplementation - Provides a defaultcreate_generatedmethodMizePartCreateThisSucksimplementation - Compatibility trait- Extern "C" function - Named
make_mize_part_<name>for dynamic loading
Custom Part Name
#[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
Defaultor implement it
Configuration
Defining Config Options
(about to be deprecad, does nothing)
Parts declare what config options they need in their opts() method:
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():
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:
[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:
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:
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:
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
// Get the auth part
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
// Use it
let token = auth.id_token()?;
// Get spacetime
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
Ok(())
}
Future Vision: Separate Crates
The goal is to move each part to its own crate following the naming convention mize-part-<name>. 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
[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
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
use serenity::Client;
#[mize_part]
#[derive(Default)]
pub struct DiscordPart {
mize: Mize,
client: Option<Client>,
}
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:
[dependencies]
mize = { path = "../mize" }
mize-part-discord = { path = "../mize-part-discord" }
mize-part-auth = { path = "../mize-part-auth" }
In your application's main.rs:
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
cargo new --lib mize-part-greeting
cd mize-part-greeting
Step 2: Edit Cargo.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)
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:
[dependencies]
mize = { path = "../mize" }
mize-part-greeting = { path = "../mize-part-greeting" }
In your application's main.rs:
compile_in_parts! {
mize,
"greeting": mize_part_greeting::part_greeting,
};
Step 5: Use the Part
// In another part or in your CLI
let mut greeting = mize.get_part_native::<GreetingPart>("greeting")?;
println!("{}", greeting.greet());
// Change the name
greeting.set_name("Alice".to_string());
println!("{}", greeting.greet());
Step 6: Configure It
Create a config.toml:
[greeting]
name = "Bob"
formal = true
[enable]
greeting = true
Run with:
mize --config config.toml run
Best Practices
- Naming: Use
mize-part-<name>for crate names andpart_<name>for registration functions - State: Store part-specific state in the part struct
- Dependencies: Declare dependencies in
deps()and document them - Config: Declare all config options in
opts() - Error Handling: Use
MizeResultandmize_err!for errors - Documentation: Document your part's purpose, config options, and methods
Summary
- Parts are modular units of functionality in Mize
- Each part implements the
MizeParttrait - Use
#[mize_part]macro for parts with state and methods - Register parts with
mize.register_part()ormize.add_name_only_part() - Parts access config via
mize.get_config() - Parts access other parts via
mize.get_part_native::<T>() - Users enable parts via the
enableconfig option - Future: Each part will be its own crate named
mize-part-<name>
Quick Reference
Common Patterns
Register a part:
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
Get a part:
let mut part = mize.get_part_native::<MyPart>("my_part")?;
Get config:
let value = mize.get_config("my_part.setting")?.value_string()?;
Declare config option:
mize.new_opt("my_part.setting");
Define a part struct:
#[mize_part]
#[derive(Default)]
pub struct MyPart {
mize: Mize,
// your fields...
}
Registration function:
pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> {
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
Ok(())
}