tilezz 0.2.0

Utilities to work with perfect-precision polygonal tiles built on top of cyclotomic integer rings.
Documentation
//! `tileset_collect` -- CLI front-end for the collect/validate driver in
//! [`tilezz::combinatorics::collect`].
//!
//! `collect` runs a chosen fixed-point classification (nbhd / jtype / seq)
//! over a chosen tileset and optionally serializes the result to a
//! kind-tagged JSON envelope; `validate` replays such a file and
//! cross-checks it against a freshly reconstructed tileset. All the logic
//! lives in the library module; this binary only parses args and does IO.

use std::time::Instant;

use clap::{Parser, Subcommand};

use tilezz::combinatorics::collect::{self, CollectKind, Envelope};
use tilezz::geom::tileset::TileSetKind;
use tilezz::util::profile::ProfileGuard;

#[derive(Parser)]
#[command(
    name = "tileset_collect",
    about = "Collect + validate fixed-point classifications over a tileset"
)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Run the chosen classification on the chosen tileset. If `--output`
    /// is given, the collected `Collection` is serialized to JSON (wrapped
    /// in a kind-tagged envelope so `validate` can dispatch later).
    Collect {
        #[arg(
            long,
            default_value = "hex",
            help = "hex|square|mixed|tetris|spectre|penrose"
        )]
        tileset: TileSetKind,
        #[arg(long, help = "nbhd|jtype|seq")]
        kind: CollectKind,
        #[arg(long)]
        output: Option<String>,
        #[arg(long, help = "Flamegraph output path (requires --features debug)")]
        pprof: Option<String>,
    },
    /// Replay a saved collection file: reconstruct its tileset from the
    /// embedded angle sequences, then run the kind-specific cross-checks.
    Validate {
        #[arg(long)]
        input: String,
    },
}

fn main() {
    let cli = Cli::parse();

    match cli.command {
        Commands::Collect {
            tileset,
            kind,
            output,
            pprof,
        } => {
            let profile = ProfileGuard::start(pprof.as_deref());

            let payload = collect::run_collect(tileset, kind);

            if let Some(path) = output {
                let env = Envelope { kind, payload };
                let t_write = Instant::now();
                let file =
                    std::fs::File::create(&path).unwrap_or_else(|e| panic!("create {path}: {e}"));
                serde_json::to_writer(std::io::BufWriter::new(file), &env)
                    .unwrap_or_else(|e| panic!("serialize {path}: {e}"));
                eprintln!("  Wrote {} in {:.2?}", path, t_write.elapsed());
            }

            profile.finish();
        }
        Commands::Validate { input } => {
            eprintln!("=== Validating: {} ===", input);
            let file = std::fs::File::open(&input).unwrap_or_else(|e| {
                eprintln!("Open error: {e}");
                std::process::exit(1);
            });
            let env: Envelope = serde_json::from_reader(std::io::BufReader::new(file))
                .unwrap_or_else(|e| {
                    eprintln!("Parse error: {e}");
                    std::process::exit(1);
                });

            match collect::run_validate(env) {
                Ok(()) => println!("OK"),
                Err(e) => {
                    eprintln!("FAIL: {e}");
                    std::process::exit(1);
                }
            }
        }
    }
}