Files
marathon/.serena/memories/code_style_conventions.md
Sienna Meridian Satterwhite 99e31b1157 removed bincode for rkyv
Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
2025-12-17 19:20:34 +00:00

294 lines
7.3 KiB
Markdown

# Code Style & Conventions
## Rust Style Configuration
The project uses **rustfmt** with a custom configuration (`rustfmt.toml`):
### Key Formatting Rules
- **Edition**: 2021
- **Braces**: `PreferSameLine` for structs/enums, `AlwaysSameLine` for control flow
- **Function Layout**: `Tall` (each parameter on its own line for long signatures)
- **Single-line Functions**: Disabled (`fn_single_line = false`)
- **Imports**:
- Grouping: `StdExternalCrate` (std, external, then local)
- Layout: `Vertical` (one import per line)
- Granularity: `Crate` level
- Reorder: Enabled
- **Comments**:
- Width: 80 characters
- Wrapping: Enabled
- Format code in doc comments: Enabled
- **Doc Attributes**: Normalized (`normalize_doc_attributes = true`)
- **Impl Items**: Reordered (`reorder_impl_items = true`)
- **Match Arms**: Leading pipes always shown
- **Hex Literals**: Lowercase
### Applying Formatting
```bash
# Format all code
cargo fmt
# Check without modifying
cargo fmt -- --check
```
## Naming Conventions
### Rust Standard Conventions
- **Types** (structs, enums, traits): `PascalCase`
- Example: `EngineBridge`, `PersistenceConfig`, `SessionId`
- **Functions & Methods**: `snake_case`
- Example: `run_executor()`, `get_database_path()`
- **Constants**: `SCREAMING_SNAKE_CASE`
- Example: `APP_NAME`, `DEFAULT_BUFFER_SIZE`
- **Variables**: `snake_case`
- Example: `engine_bridge`, `db_path_str`
- **Modules**: `snake_case`
- Example: `debug_ui`, `engine_bridge`
- **Crates**: `kebab-case` in Cargo.toml, `snake_case` in code
- Example: `sync-macros``sync_macros`
### Project-Specific Patterns
- **Platform modules**: `platform/desktop/`, `platform/ios/`
- **Plugin naming**: Suffix with `Plugin` (e.g., `EngineBridgePlugin`, `CameraPlugin`)
- **Resource naming**: Prefix with purpose (e.g., `PersistenceConfig`, `SessionManager`)
- **System naming**: Suffix with `_system` for Bevy systems
- **Bridge pattern**: Use `Bridge` suffix for inter-component communication (e.g., `EngineBridge`)
## Code Organization
### Module Structure
```rust
// Public API first
pub mod engine;
pub mod networking;
pub mod persistence;
// Internal modules
mod debug_ui;
mod platform;
// Re-exports for convenience
pub use engine::{EngineCore, EngineBridge};
```
### Import Organization
```rust
// Standard library
use std::sync::Arc;
use std::thread;
// External crates (grouped by crate)
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;
// Internal crates
use libmarathon::engine::EngineCore;
use libmarathon::platform;
// Local modules
use crate::camera::*;
use crate::debug_ui::DebugUiPlugin;
```
## Documentation
### Doc Comments
- Use `///` for public items
- Use `//!` for module-level documentation
- Include examples where helpful
- Document panics, errors, and safety considerations
```rust
/// Creates a new engine bridge for communication between Bevy and EngineCore.
///
/// # Returns
///
/// A tuple of `(EngineBridge, EngineHandle)` where the bridge goes to Bevy
/// and the handle goes to EngineCore.
///
/// # Examples
///
/// ```no_run
/// let (bridge, handle) = EngineBridge::new();
/// app.insert_resource(bridge);
/// // spawn EngineCore with handle
/// ```
pub fn new() -> (EngineBridge, EngineHandle) {
// ...
}
```
### Code Comments
- Keep line comments at 80 characters or less
- Explain *why*, not *what* (code should be self-documenting for the "what")
- Use `// TODO:` for temporary code that needs improvement
- Use `// SAFETY:` before unsafe blocks to explain invariants
## Error Handling
### Library Code (libmarathon)
- Use `thiserror` for custom error types
- Return `Result<T, Error>` from fallible functions
- Provide context with error chains
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum EngineError {
#[error("failed to connect to peer: {0}")]
ConnectionFailed(String),
#[error("database error: {0}")]
Database(#[from] rusqlite::Error),
}
```
### Application Code (app)
- Use `anyhow::Result` for application-level error handling
- Add context with `.context()` or `.with_context()`
```rust
use anyhow::{Context, Result};
fn load_config() -> Result<Config> {
let path = get_config_path()
.context("failed to determine config path")?;
std::fs::read_to_string(&path)
.with_context(|| format!("failed to read config from {:?}", path))?
// ...
}
```
## Async/Await Style
### Tokio Runtime Usage
- Spawn blocking tasks in background threads
- Use `tokio::spawn` for async tasks
- Prefer `async fn` over `impl Future`
```rust
// Good: Clear async function
async fn process_events(&mut self) -> Result<()> {
// ...
}
// Background task spawning
std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
core.run().await;
});
});
```
## Testing Conventions
### Test Organization
- Unit tests: In same file as code (`#[cfg(test)] mod tests`)
- Integration tests: In `tests/` directory
- Benchmarks: In `benches/` directory
### Test Naming
- Use descriptive names: `test_sync_between_two_nodes`
- Use `should_` prefix for behavior tests: `should_reject_invalid_input`
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_engine_bridge_creation() {
let (bridge, handle) = EngineBridge::new();
// ...
}
#[tokio::test]
async fn should_sync_state_across_peers() {
// ...
}
}
```
## Platform-Specific Code
### Feature Gates
```rust
// iOS-specific code
#[cfg(target_os = "ios")]
use tracing_oslog::OsLogger;
// Desktop-specific code
#[cfg(not(target_os = "ios"))]
use tracing_subscriber::fmt;
```
### Platform Modules
- Keep platform-specific code in `platform/` modules
- Provide platform-agnostic interfaces when possible
- Use feature flags: `desktop`, `ios`, `headless`
## Logging
### Use Structured Logging
```rust
use tracing::{debug, info, warn, error};
// Good: Structured with context
info!(path = %db_path, "opening database");
debug!(count = peers.len(), "connected to peers");
// Avoid: Plain string
info!("Database opened at {}", db_path);
```
### Log Levels
- `error!`: System failures requiring immediate attention
- `warn!`: Unexpected conditions that are handled
- `info!`: Important state changes and milestones
- `debug!`: Detailed diagnostic information
- `trace!`: Very verbose, rarely needed
## RFCs and Design Documentation
### When to Write an RFC
- Architectural decisions affecting multiple parts
- Choosing between significantly different approaches
- Introducing new protocols or APIs
- Making breaking changes
### RFC Structure (see `docs/rfcs/README.md`)
- Narrative-first explanation
- Trade-offs and alternatives
- API examples (not full implementations)
- Open questions
- Success criteria
## Git Commit Messages
### Format
```
Brief summary (50 chars or less)
More detailed explanation if needed. Wrap at 72 characters.
- Use bullet points for multiple changes
- Reference issue numbers: #123
Explains trade-offs, alternatives considered, and why this approach
was chosen.
```
### Examples
```
Add CRDT synchronization over iroh-gossip
Implements the protocol described in RFC 0001. Uses vector clocks
for causal ordering and merkle trees for efficient reconciliation.
- Add VectorClock type
- Implement GossipBridge for peer communication
- Add integration tests for two-peer sync
```