use crate::{
curves::{gray, hairyonion, hcurve, hilbert, onion, scan, zorder},
error,
spacecurve::SpaceCurve,
spec::GridSpec,
};
pub struct CurveEntry {
pub key: &'static str,
pub display: &'static str,
pub constraints: &'static str,
pub experimental: bool,
pub build_spec: fn(u32, u32) -> error::Result<GridSpec>,
pub ctor: fn(&GridSpec) -> error::Result<Box<dyn SpaceCurve + 'static>>,
}
fn v_hilbert(dim: u32, size: u32) -> error::Result<GridSpec> {
let spec = GridSpec::power_of_two(dim, size)?;
let total_bits = (spec.order().unwrap() as u64) * (dim as u64);
if total_bits >= 32 {
return Err(error::Error::Size(
"Hilbert requires order * dimension < 32 for u32 indices".to_string(),
));
}
Ok(spec)
}
fn v_hcurve(dim: u32, size: u32) -> error::Result<GridSpec> {
if dim < 2 {
return Err(error::Error::Shape("dimension must be >= 2".to_string()));
}
let spec = GridSpec::power_of_two(dim, size)?;
if dim >= 32 {
return Err(error::Error::Shape("dimension must be < 32".to_string()));
}
if (spec.order().unwrap() as u64) * (dim as u64) >= 32 {
return Err(error::Error::Size(
"Curve size exceeds u32 limits (D*O must be < 32)".to_string(),
));
}
Ok(spec)
}
fn v_zorder(dim: u32, size: u32) -> error::Result<GridSpec> {
let spec = GridSpec::power_of_two(dim, size)?;
spec.require_index_bits_lt(32)?;
Ok(spec)
}
fn v_onion(dim: u32, size: u32) -> error::Result<GridSpec> {
GridSpec::new(dim, size)
}
fn v_hairyonion(dim: u32, size: u32) -> error::Result<GridSpec> {
GridSpec::new(dim, size)
}
fn v_scan(dim: u32, size: u32) -> error::Result<GridSpec> {
GridSpec::new(dim, size)
}
fn v_gray(dim: u32, size: u32) -> error::Result<GridSpec> {
let spec = GridSpec::power_of_two(dim, size)?;
if (spec.bits_per_axis().unwrap() as u64) * (dim as u64) >= 32 {
return Err(error::Error::Size(
"Gray requires bitwidth * dimension < 32 for u32 indices".to_string(),
));
}
Ok(spec)
}
fn c_hilbert(spec: &GridSpec) -> error::Result<Box<dyn SpaceCurve + 'static>> {
Ok(Box::new(hilbert::Hilbert::from_dimensions(
spec.dimension(),
spec.size(),
)?))
}
fn c_hcurve(spec: &GridSpec) -> error::Result<Box<dyn SpaceCurve + 'static>> {
Ok(Box::new(hcurve::HCurve::from_dimensions(
spec.dimension(),
spec.size(),
)?))
}
fn c_zorder(spec: &GridSpec) -> error::Result<Box<dyn SpaceCurve + 'static>> {
Ok(Box::new(zorder::ZOrder::from_dimensions(
spec.dimension(),
spec.size(),
)?))
}
fn c_onion(spec: &GridSpec) -> error::Result<Box<dyn SpaceCurve + 'static>> {
Ok(Box::new(onion::OnionCurve::new(
spec.dimension(),
spec.size(),
)?))
}
fn c_hairyonion(spec: &GridSpec) -> error::Result<Box<dyn SpaceCurve + 'static>> {
Ok(Box::new(hairyonion::HairyOnionCurve::new(
spec.dimension(),
spec.size(),
)?))
}
fn c_scan(spec: &GridSpec) -> error::Result<Box<dyn SpaceCurve + 'static>> {
Ok(Box::new(scan::Scan::from_dimensions(
spec.dimension(),
spec.size(),
)?))
}
fn c_gray(spec: &GridSpec) -> error::Result<Box<dyn SpaceCurve + 'static>> {
Ok(Box::new(gray::Gray::from_dimensions(
spec.dimension(),
spec.size(),
)?))
}
macro_rules! define_registry {
( $(
{
$key:literal,
$display:literal,
$constraints:literal,
$experimental:expr,
$validate:ident,
$ctor:ident
}
),+ $(,)? ) => {
pub const CURVE_NAMES: &[&str] = &[ $( $key ),+ ];
pub static REGISTRY: &[CurveEntry] = &[
$(
CurveEntry {
key: $key,
display: $display,
constraints: $constraints,
experimental: $experimental,
build_spec: $validate,
ctor: $ctor,
},
)+
];
};
}
define_registry! {
{ "hilbert", "Hilbert", "size=2^order; order*dimension < 32 (u32 indices)", false, v_hilbert, c_hilbert },
{ "scan", "Scan", "any size>=1; any dimension>=1", false, v_scan, c_scan },
{ "zorder", "Z-order (Morton)", "size=2^bitwidth; bitwidth*dimension < 32 (u32 indices)", false, v_zorder, c_zorder },
{ "hcurve", "H-curve", "dimension>=2; size=2^order; order*dimension < 32", false, v_hcurve, c_hcurve },
{ "onion", "Onion", "any size>=1; any dimension>=1; length=size^dimension fits u32", false, v_onion, c_onion },
{ "hairyonion", "Hairy Onion", "any size>=1; any dimension>=1; length=size^dimension fits u32", true, v_hairyonion, c_hairyonion },
{ "gray", "Gray (BRGC)", "size=2^bitwidth; bitwidth*dimension < 32 (u32 indices)", false, v_gray, c_gray },
}
pub fn curve_names(include_experimental: bool) -> Vec<&'static str> {
REGISTRY
.iter()
.filter(|entry| include_experimental || !entry.experimental)
.map(|entry| entry.key)
.collect()
}
pub fn find(key: &str) -> Option<&'static CurveEntry> {
REGISTRY.iter().find(|e| e.key == key)
}
pub fn validate(key: &str, dimension: u32, size: u32) -> error::Result<()> {
match find(key) {
Some(entry) => {
(entry.build_spec)(dimension, size)?;
Ok(())
}
None => Err(error::Error::Unknown(format!("unknown pattern: \"{key}\""))),
}
}
pub fn construct(
key: &str,
dimension: u32,
size: u32,
) -> error::Result<Box<dyn SpaceCurve + 'static>> {
match find(key) {
Some(entry) => {
let spec = (entry.build_spec)(dimension, size)?;
(entry.ctor)(&spec)
}
None => Err(error::Error::Unknown(format!("unknown pattern: \"{key}\""))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_consistency() {
let mut registry_keys: Vec<&str> = REGISTRY.iter().map(|e| e.key).collect();
let mut names_list: Vec<&str> = CURVE_NAMES.to_vec();
registry_keys.sort();
names_list.sort();
assert_eq!(
registry_keys, names_list,
"REGISTRY keys and CURVE_NAMES must be identical"
);
for (i, name) in CURVE_NAMES.iter().enumerate() {
assert_eq!(
REGISTRY[i].key, *name,
"REGISTRY and CURVE_NAMES order mismatch at index {}",
i
);
}
}
}