hail_core 0.3.0

a library for implementing a speedrun timer
Documentation
/*
  Copyright 2026 periwinkle

  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

use super::Run;

use serde::{Deserialize, Serialize};

/// Run struct used for runfile v5+ operations.
///
/// This places some more restrictions on run values - for example,
/// using u64 for times that are only allowed to be positive. Additionally,
/// uses `Option` instead of `TimeType` to reduce visual clutter in the run file,
/// since `ron` can elide `Some()` but not `Time()`.
#[derive(Serialize, Deserialize)]
pub struct SerDesRun {
    pub game_title: String,
    pub category: String,
    /// Whether the run saves ingame time.
    pub ingame_time: bool,
    /// Time that the timer starts at. Can be positive or negative.
    pub offset: Option<i128>,
    pub segment_names: Vec<String>,
    /// Splits set in real-time for the fastest completed run.
    pub rta_pb_splits: Vec<Option<u64>>,
    /// Splits set in game-time for the fastest completed run.
    pub igt_pb_splits: Vec<Option<u64>>,
    /// Fastest individual segments set in real-time.
    pub rta_gold_segments: Vec<Option<u64>>,
    /// Fastest individual segments set in game-time.
    pub igt_gold_segments: Vec<Option<u64>>,
    /// Total number of attempts and total time spent on each segment in real-time.
    pub rta_sum_segments: Vec<(usize, Option<u64>)>,
    /// Total number of attempts and total time spent on each segment in game-time.
    pub igt_sum_segments: Vec<(usize, Option<u64>)>,
}

impl From<SerDesRun> for Run {
    fn from(r: SerDesRun) -> Run {
        Run {
            game_title: r.game_title,
            category: r.category,
            ingame_time: r.ingame_time,
            offset: r.offset.into(),
            segment_names: r.segment_names,
            rta_pb_splits: r.rta_pb_splits.iter().map(|&t| t.into()).collect(),
            igt_pb_splits: r.igt_pb_splits.iter().map(|&t| t.into()).collect(),
            rta_gold_segments: r.rta_gold_segments.iter().map(|&t| t.into()).collect(),
            igt_gold_segments: r.igt_gold_segments.iter().map(|&t| t.into()).collect(),
            rta_sum_segments: r
                .rta_sum_segments
                .iter()
                .map(|&(n, t)| (n, t.into()))
                .collect(),
            igt_sum_segments: r
                .igt_sum_segments
                .iter()
                .map(|&(n, t)| (n, t.into()))
                .collect(),
            ..Default::default()
        }
    }
}

impl From<&Run> for SerDesRun {
    fn from(r: &Run) -> SerDesRun {
        let mut ret = SerDesRun {
            game_title: r.game_title.to_owned(),
            category: r.category.to_owned(),
            ingame_time: r.ingame_time,
            offset: r.offset.into(),
            segment_names: vec![],
            rta_pb_splits: r.rta_pb_splits.iter().map(|&t| t.into()).collect(),
            igt_pb_splits: r.igt_pb_splits.iter().map(|&t| t.into()).collect(),
            rta_gold_segments: r.rta_gold_segments.iter().map(|&t| t.into()).collect(),
            igt_gold_segments: r.igt_gold_segments.iter().map(|&t| t.into()).collect(),
            rta_sum_segments: r
                .rta_sum_segments
                .iter()
                .map(|&(n, t)| (n, t.into()))
                .collect(),
            igt_sum_segments: r
                .igt_sum_segments
                .iter()
                .map(|&(n, t)| (n, t.into()))
                .collect(),
        };
        r.segment_names.clone_into(&mut ret.segment_names);
        ret
    }
}