Skip to main content

phichain-compiler

The phichain-compiler crate compiles Phichain charts into a primitive format suitable for gameplay. It performs optimizations like merging child lines, evaluating curve note tracks, and flattening the chart structure.

Installation

Add this to your Cargo.toml:
[dependencies]
phichain-compiler = "1.0.0-beta.5"
phichain-chart = "1.0.0-beta.5"

Dependencies

The crate depends on:
  • phichain-chart (chart data structures)
  • num 0.4.3 (numerical computations)
  • anyhow 1.0 (error handling)
  • nalgebra 0.33.1 (linear algebra for transformations)

Core Function

compile
function
Compile a Phichain chart into a primitive chart format.The compilation process:
  1. Merges child lines into their parents with proper transformations
  2. Evaluates curve note tracks to generate individual notes
  3. Converts to primitive chart format
use phichain_compiler::compile;
use phichain_chart::serialization::PhichainChart;
use phichain_chart::primitive::PrimitiveChart;
use std::fs::File;

fn main() -> anyhow::Result<()> {
    // Load chart
    let file = File::open("chart.json")?;
    let chart: PhichainChart = serde_json::from_reader(file)?;
    
    // Compile to primitive format
    let primitive = compile(chart)?;
    
    // Save compiled chart
    let output = File::create("chart.compiled.json")?;
    serde_json::to_writer_pretty(output, &primitive)?;
    
    Ok(())
}

Compilation Steps

1. Merge Child Lines

Child lines are merged into their parent lines by applying transformations:
  • Position offsets are combined
  • Rotations are accumulated
  • Events are transformed to parent coordinate space
  • Notes are repositioned relative to parent
use phichain_chart::serialization::{PhichainChart, SerializedLine};
use phichain_chart::line::Line;
use phichain_chart::beat;

// Before compilation: parent with child line
let parent = SerializedLine::new(
    Line { name: "Parent".to_string() },
    vec![],
    vec![],  // Parent events
    vec![
        SerializedLine::new(
            Line { name: "Child".to_string() },
            vec![],  // Child notes
            vec![],  // Child events
            vec![],
            vec![],
        )
    ],
    vec![],
);

// After compilation: child merged into parent
// All child transformations are computed relative to parent

2. Evaluate Curve Note Tracks

Curve note tracks define smooth note patterns between two anchor notes. The compiler:
  • Samples points along the bezier curve
  • Generates individual notes at each sample point
  • Interpolates properties (speed, x position) along the curve
  • Removes the curve track definition
use phichain_chart::curve_note_track::CurveNoteTrack;
use phichain_chart::note::{Note, NoteKind};
use phichain_chart::beat;

// Before compilation: curve track between notes
let from_note = Note::new(NoteKind::Tap, true, beat!(0), -1.0, 1.0);
let to_note = Note::new(NoteKind::Tap, true, beat!(4), 1.0, 1.0);

// Curve track generates notes along the path
// After compilation: multiple individual notes along the curve

3. Convert to Primitive Format

The final primitive chart:
  • Has a flat line structure (no nesting)
  • Contains only basic note types
  • Uses simple transition events
  • Is optimized for fast rendering and gameplay

Primitive Chart Format

PrimitiveChart
struct
The compiled primitive chart format.
use phichain_chart::primitive::PrimitiveChart;

pub struct PrimitiveChart {
    pub offset: f32,
    pub bpm_list: BpmList,
    pub lines: Vec<primitive::line::Line>,
    // Additional metadata fields
}

Complete Example

use phichain_compiler::compile;
use phichain_chart::{
    serialization::PhichainChart,
    primitive::PrimitiveChart,
};
use std::fs::File;
use std::path::PathBuf;

fn main() -> anyhow::Result<()> {
    // Load source chart
    let input_path = PathBuf::from("charts/mysong/chart.json");
    let file = File::open(&input_path)?;
    let chart: PhichainChart = serde_json::from_reader(file)?;
    
    println!("Loaded chart with {} lines", chart.lines.len());
    
    // Compile the chart
    println!("Compiling...");
    let primitive = compile(chart)?;
    
    println!("Compiled to {} flattened lines", primitive.lines.len());
    
    // Save compiled chart
    let output_path = PathBuf::from("charts/mysong/chart.compiled.json");
    let output = File::create(&output_path)?;
    serde_json::to_writer_pretty(output, &primitive)?;
    
    println!("Saved to {}", output_path.display());
    
    Ok(())
}

Understanding Compilation

Why Compile?

Phichain charts support advanced features like:
  • Nested lines - Child lines with relative transformations
  • Curve note tracks - Smooth note patterns defined by bezier curves
  • Complex events - Layered transformations and animations
These features make authoring easier but are expensive to compute in real-time. Compilation converts these high-level features into a simple, flat format optimized for gameplay.

Performance Benefits

  1. Flattened hierarchy - Eliminates nested line transformations during gameplay
  2. Pre-computed notes - Curve tracks are expanded into individual notes
  3. Simplified events - Complex event chains are resolved
  4. Reduced memory - Compact primitive format uses less memory
  5. Faster parsing - Simple structure for quick deserialization

When to Compile

  • Before distribution - Ship compiled charts for better performance
  • Chart validation - Verify charts compile without errors
  • Performance testing - Test gameplay with optimized charts
  • Cross-platform - Ensure charts work on all target platforms
  • Editor preview - Quick preview of compiled output

Advanced Usage

Custom Compilation Pipeline

You can implement custom compilation steps:
use phichain_chart::serialization::PhichainChart;
use phichain_chart::primitive::PrimitiveChart;

fn custom_compile(mut chart: PhichainChart) -> anyhow::Result<PrimitiveChart> {
    // 1. Custom preprocessing
    preprocess(&mut chart)?;
    
    // 2. Standard compilation
    let mut primitive = phichain_compiler::compile(chart)?;
    
    // 3. Custom postprocessing
    postprocess(&mut primitive)?;
    
    Ok(primitive)
}

fn preprocess(chart: &mut PhichainChart) -> anyhow::Result<()> {
    // Add custom logic before compilation
    // e.g., normalize BPM, validate chart structure
    Ok(())
}

fn postprocess(chart: &mut PrimitiveChart) -> anyhow::Result<()> {
    // Add custom logic after compilation
    // e.g., optimize note ordering, merge events
    Ok(())
}

Error Handling

Compilation can fail for several reasons:
use phichain_compiler::compile;
use phichain_chart::serialization::PhichainChart;
use std::fs::File;

fn safe_compile(path: &str) -> anyhow::Result<()> {
    let file = File::open(path)
        .map_err(|e| anyhow::anyhow!("Failed to open file: {}", e))?;
    
    let chart: PhichainChart = serde_json::from_reader(file)
        .map_err(|e| anyhow::anyhow!("Failed to parse chart: {}", e))?;
    
    let primitive = compile(chart)
        .map_err(|e| anyhow::anyhow!("Compilation failed: {}", e))?;
    
    // Use compiled chart
    println!("Successfully compiled {} lines", primitive.lines.len());
    
    Ok(())
}

See Also