livesplit_core/run/parser/
splitterino.rs1use crate::{platform::prelude::*, Run, Segment, Time, 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#[derive(Deserialize)]
26struct SplitsFormat<'a> {
27 #[serde(borrow)]
30 splits: Splits<'a>,
31}
32
33#[derive(Deserialize)]
35#[serde(rename_all = "camelCase")]
36struct Splits<'a> {
37 #[serde(borrow)]
39 game: GameInfo<'a>,
40 start_delay: Option<i64>,
42 #[serde(borrow)]
44 segments: Vec<SplitterinoSegment<'a>>,
45 }
48
49#[derive(Deserialize)]
51#[serde(rename_all = "camelCase")]
52enum SplitterinoTimingMethod {
53 Igt,
54 Rta,
55}
56
57#[derive(Deserialize, Default)]
59#[serde(default)]
60struct GameInfo<'a> {
61 #[serde(borrow)]
63 name: Cow<'a, str>,
64 #[serde(borrow)]
66 category: Cow<'a, str>,
67 #[serde(borrow)]
69 platform: Cow<'a, str>,
70 #[serde(borrow)]
72 region: Cow<'a, str>,
73}
74
75#[derive(Deserialize)]
77#[serde(rename_all = "camelCase")]
78struct SplitterinoSegment<'a> {
79 #[serde(borrow)]
84 name: Cow<'a, str>,
85 personal_best: Option<SegmentTime>,
87 overall_best: Option<SegmentTime>,
89 #[serde(default)]
93 skipped: bool,
94}
95
96#[derive(Deserialize)]
98struct SegmentTime {
99 igt: DetailedTime,
101 rta: DetailedTime,
103}
104
105#[derive(Deserialize)]
107#[serde(rename_all = "camelCase")]
108struct DetailedTime {
109 raw_time: u64,
111 pause_time: u64,
113}
114
115fn parse_segment_time(SegmentTime { igt, rta }: SegmentTime) -> [TimeSpan; 2] {
116 [
117 TimeSpan::from_milliseconds((rta.raw_time - rta.pause_time) as _),
118 TimeSpan::from_milliseconds((igt.raw_time - igt.pause_time) as _),
119 ]
120}
121
122fn parse_best_segment_time(segment_time: SegmentTime) -> Time {
123 let [rta, igt] = parse_segment_time(segment_time);
124 let mut time = Time::new();
125 if rta != TimeSpan::zero() {
126 time.real_time = Some(rta);
127 }
128 if igt != TimeSpan::zero() {
129 time.game_time = Some(igt);
130 }
131 time
132}
133
134fn parse_split_time(
135 total_rta: &mut TimeSpan,
136 total_igt: &mut TimeSpan,
137 segment_time: SegmentTime,
138) -> Time {
139 let [rta, igt] = parse_segment_time(segment_time);
140
141 let real_time = if rta != TimeSpan::zero() {
142 *total_rta += rta;
143 Some(*total_rta)
144 } else {
145 None
146 };
147
148 let game_time = if igt != TimeSpan::zero() {
149 *total_igt += igt;
150 Some(*total_igt)
151 } else {
152 None
153 };
154
155 Time {
156 real_time,
157 game_time,
158 }
159}
160
161pub fn parse(source: &str) -> Result<Run> {
163 let SplitsFormat::<'_> { splits, .. } =
164 serde_json::from_str(source).map_err(|source| Error::Json { source })?;
165
166 let mut run = Run::new();
167
168 run.set_game_name(splits.game.name);
169 run.set_category_name(splits.game.category);
170 run.metadata_mut().set_platform_name(splits.game.platform);
171 run.metadata_mut().set_region_name(splits.game.region);
172
173 if let Some(start_delay) = splits.start_delay {
178 run.set_offset(TimeSpan::from_milliseconds(-start_delay as _));
179 }
180
181 let (mut total_rta, mut total_igt) = (TimeSpan::zero(), TimeSpan::zero());
182
183 run.segments_mut()
184 .extend(splits.segments.into_iter().map(|split| {
185 let mut segment = Segment::new(split.name);
186
187 if !split.skipped {
188 if let Some(personal_best) = split.personal_best {
189 segment.set_personal_best_split_time(parse_split_time(
190 &mut total_rta,
191 &mut total_igt,
192 personal_best,
193 ));
194 }
195 }
196
197 if let Some(overall_best) = split.overall_best {
198 segment.set_best_segment_time(parse_best_segment_time(overall_best));
199 }
200
201 segment
202 }));
203
204 Ok(run)
205}