livesplit_core/run/parser/
source_live_timer.rs1use crate::{platform::prelude::*, GameTime, Run, Segment, TimeSpan};
4use alloc::borrow::Cow;
5use core::result::Result as StdResult;
6use serde::Deserialize;
7use serde_json::Error as JsonError;
8
9#[derive(Debug, snafu::Snafu)]
12#[snafu(context(suffix(false)))]
13pub enum Error {
14 Json {
16 #[cfg_attr(not(feature = "std"), snafu(source(false)))]
18 source: JsonError,
19 },
20}
21
22pub type Result<T> = StdResult<T, Error>;
24
25#[allow(non_snake_case)]
26#[derive(Deserialize)]
27struct Splits<'a> {
28 #[serde(borrow)]
29 Category: Cow<'a, str>,
30 #[serde(borrow)]
31 RunName: Option<Cow<'a, str>>,
32 Splits: Option<Vec<Split<'a>>>,
33}
34
35#[allow(non_snake_case)]
36#[derive(Deserialize)]
37struct Split<'a> {
38 #[serde(borrow)]
39 Map: Cow<'a, str>,
40 #[serde(borrow)]
41 Name: Option<Cow<'a, str>>,
42 Ticks: Option<u64>,
43 BestSegment: Option<u64>,
44}
45
46fn time_span_from_ticks(is_portal2: bool, ticks: u64) -> TimeSpan {
47 let seconds = if is_portal2 {
48 ticks as f64 / 30.0
49 } else {
50 ticks as f64 / 66.666_666_666_666_7
52 };
53
54 TimeSpan::from_seconds(seconds)
55}
56
57pub fn parse(source: &str) -> Result<Run> {
59 let splits: Splits<'_> =
60 serde_json::from_str(source).map_err(|source| Error::Json { source })?;
61
62 let mut run = Run::new();
63
64 if splits.Category.starts_with("Portal 2") {
65 run.set_game_name("Portal 2");
66 } else if splits.Category.starts_with("Portal") {
67 run.set_game_name("Portal");
68 } else if splits.Category.starts_with("Half Life 2") {
69 run.set_game_name("Half Life 2");
70 }
71
72 if let Some(run_name) = splits.RunName {
73 if run_name != splits.Category {
74 run.set_category_name(run_name);
75 } else {
76 run.set_category_name(splits.Category);
77 }
78 } else {
79 run.set_category_name(splits.Category);
80 }
81
82 if let Some(segments) = splits.Splits {
83 let is_portal2 = run.category_name().starts_with("Portal 2");
84
85 run.segments_mut().extend(segments.into_iter().map(|split| {
86 let mut segment = Segment::new(split.Name.unwrap_or(split.Map));
87
88 if let Some(ticks) = split.Ticks {
89 let pb_split_time = Some(time_span_from_ticks(is_portal2, ticks));
90 segment.set_personal_best_split_time(GameTime(pb_split_time).into());
91 }
92
93 if let Some(best) = split.BestSegment {
94 let best_segment_time = Some(time_span_from_ticks(is_portal2, best));
95 segment.set_best_segment_time(GameTime(best_segment_time).into());
96 }
97
98 segment
99 }));
100 }
101
102 Ok(run)
103}