Skip to content

Commit 39f75ce

Browse files
authored
1 parent 02d2bec commit 39f75ce

File tree

2 files changed

+89
-74
lines changed

2 files changed

+89
-74
lines changed

src/run/parser/opensplit.rs

Lines changed: 88 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// https://github.com/ZellyDev-Games/OpenSplit
44

55
use crate::{Run, Segment, Time, TimeSpan, platform::prelude::*};
6-
use alloc::borrow::Cow;
6+
use alloc::{borrow::Cow, collections::BTreeMap};
77
use core::result::Result as StdResult;
88
use serde_derive::Deserialize;
99
use serde_json::Error as JsonError;
@@ -30,26 +30,29 @@ struct SplitFilePayload<'a> {
3030
game_name: Cow<'a, str>,
3131
#[serde(borrow)]
3232
game_category: Cow<'a, str>,
33-
segments: Option<Vec<SegmentPayload<'a>>>,
33+
#[serde(default)]
34+
segments: Vec<SegmentPayload<'a>>,
3435
attempts: u32,
35-
runs: Option<Vec<RunPayload<'a>>>,
36+
#[serde(default)]
37+
runs: Vec<RunPayload<'a>>,
38+
#[serde(default)]
39+
offset: i64,
40+
#[serde(default, borrow)]
41+
platform: Cow<'a, str>,
3642
}
3743

3844
#[derive(Deserialize)]
39-
#[serde(rename_all = "camelCase")]
4045
struct RunPayload<'a> {
4146
total_time: i64,
4247
completed: bool,
43-
#[serde(borrow)]
44-
split_payloads: Option<Vec<SplitPayload<'a>>>,
48+
#[serde(default, borrow)]
49+
splits: BTreeMap<Cow<'a, str>, SplitPayload>,
4550
}
4651

4752
#[derive(Deserialize)]
48-
struct SplitPayload<'a> {
49-
#[serde(borrow)]
50-
split_segment_id: Cow<'a, str>,
51-
// FIXME: Is current_time the correct field?
52-
// current_time: TimeSpan,
53+
struct SplitPayload {
54+
#[allow(dead_code)]
55+
current_cumulative: i64,
5356
current_duration: i64,
5457
}
5558

@@ -59,9 +62,10 @@ struct SegmentPayload<'a> {
5962
id: Cow<'a, str>,
6063
#[serde(borrow)]
6164
name: Cow<'a, str>,
62-
best_time: TimeSpan,
63-
// FIXME: Would need to be stored as part of the segment history
64-
// average_time: TimeSpan,
65+
gold: i64,
66+
pb: i64,
67+
#[serde(default, borrow)]
68+
children: Vec<SegmentPayload<'a>>,
6569
}
6670

6771
fn nullable(real_time: TimeSpan) -> Time {
@@ -75,8 +79,24 @@ fn nullable(real_time: TimeSpan) -> Time {
7579
Time::new().with_real_time(real_time)
7680
}
7781

78-
fn integer_time(nanos: i64) -> TimeSpan {
79-
crate::platform::Duration::nanoseconds(nanos).into()
82+
fn integer_time(milliseconds: i64) -> TimeSpan {
83+
crate::platform::Duration::milliseconds(milliseconds).into()
84+
}
85+
86+
fn flatten_leaf_segments<'a>(segments: Vec<SegmentPayload<'a>>) -> Vec<SegmentPayload<'a>> {
87+
let mut leaf_segments = Vec::with_capacity(segments.len());
88+
let mut stack = Vec::with_capacity(segments.len());
89+
stack.extend(segments.into_iter().rev());
90+
91+
while let Some(mut segment) = stack.pop() {
92+
if segment.children.is_empty() {
93+
leaf_segments.push(segment);
94+
} else {
95+
stack.extend(segment.children.drain(..).rev());
96+
}
97+
}
98+
99+
leaf_segments
80100
}
81101

82102
/// Attempts to parse an OpenSplit splits file.
@@ -89,70 +109,65 @@ pub fn parse(source: &str) -> Result<Run> {
89109
run.set_game_name(splits.game_name);
90110
run.set_category_name(splits.game_category);
91111
run.set_attempt_count(splits.attempts);
112+
run.set_offset(integer_time(splits.offset));
113+
run.metadata_mut().set_platform_name(splits.platform);
92114

93-
if let Some(segments) = splits.segments {
94-
let mut segment_ids = Vec::with_capacity(segments.len());
115+
let leaf_segments = flatten_leaf_segments(splits.segments);
95116

96-
for segment_payload in segments {
97-
segment_ids.push(segment_payload.id);
98-
let mut segment = Segment::new(segment_payload.name);
99-
segment.set_personal_best_split_time(nullable(segment_payload.best_time));
100-
run.push_segment(segment);
101-
}
117+
let mut segment_ids = Vec::with_capacity(leaf_segments.len());
118+
let mut cumulative_pb = TimeSpan::zero();
102119

103-
let mut attempt_history_index = 1;
104-
105-
if let Some(runs) = splits.runs {
106-
for run_payload in runs {
107-
run.add_attempt_with_index(
108-
Time::new().with_real_time(if run_payload.completed {
109-
Some(integer_time(run_payload.total_time))
110-
} else {
111-
None
112-
}),
113-
attempt_history_index,
114-
None,
115-
None,
116-
None,
117-
);
118-
119-
let mut current_time = 0;
120-
let mut previous_idx = None;
121-
122-
if let Some(split_payloads) = run_payload.split_payloads {
123-
for split_payload in split_payloads {
124-
if let Some(idx) = segment_ids
125-
.iter()
126-
.position(|id| *id == split_payload.split_segment_id)
127-
&& previous_idx.is_none_or(|prev| idx > prev)
128-
{
129-
let segment_time = split_payload.current_duration - current_time;
130-
131-
run.segments_mut()[idx].segment_history_mut().insert(
132-
attempt_history_index,
133-
Time::new().with_real_time(Some(integer_time(segment_time))),
134-
);
135-
136-
current_time = split_payload.current_duration;
137-
previous_idx = Some(idx);
138-
}
139-
}
140-
}
141-
142-
attempt_history_index += 1;
143-
}
144-
}
120+
for segment_payload in leaf_segments {
121+
segment_ids.push(segment_payload.id);
122+
123+
let mut segment = Segment::new(segment_payload.name);
124+
125+
segment.set_best_segment_time(nullable(integer_time(segment_payload.gold)));
126+
127+
cumulative_pb += integer_time(segment_payload.pb);
128+
segment.set_personal_best_split_time(Time::new().with_real_time(
129+
if segment_payload.pb != 0 {
130+
Some(cumulative_pb)
131+
} else {
132+
None
133+
},
134+
));
135+
136+
run.push_segment(segment);
145137
}
146138

147-
for segment in run.segments_mut() {
148-
if let Some(segment_time) = segment
149-
.segment_history()
139+
let mut attempt_history_index = 1;
140+
141+
for run_payload in splits.runs {
142+
run.add_attempt_with_index(
143+
Time::new().with_real_time(if run_payload.completed {
144+
Some(integer_time(run_payload.total_time))
145+
} else {
146+
None
147+
}),
148+
attempt_history_index,
149+
None,
150+
None,
151+
None,
152+
);
153+
154+
let last = segment_ids
150155
.iter()
151-
.filter_map(|(_, time)| time.real_time)
152-
.min()
153-
{
154-
segment.set_best_segment_time(Time::new().with_real_time(Some(segment_time)));
156+
.enumerate()
157+
.rfind(|(_, segment_id)| run_payload.splits.contains_key(*segment_id))
158+
.map_or(0, |(index, _)| index + 1);
159+
160+
for (segment_id, segment) in segment_ids[..last].iter().zip(run.segments_mut()) {
161+
let mut time = Time::new();
162+
if let Some(split_payload) = run_payload.splits.get(segment_id) {
163+
time = time.with_real_time(Some(integer_time(split_payload.current_duration)));
164+
}
165+
segment
166+
.segment_history_mut()
167+
.insert(attempt_history_index, time);
155168
}
169+
170+
attempt_history_index += 1;
156171
}
157172

158173
Ok(run)

tests/run_files/OpenSplit.osf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"id":"f348af3a-6dd5-4d56-b8d2-e2ee9f34d225","version":1,"game_name":"Foo","game_category":"Bar","segments":[{"id":"0fc8317a-454a-4799-95f9-9bb4eb45a2b6","name":"A","best_time":"0:01:00.00","average_time":"0:00:00.00"},{"id":"ee1f9846-c925-4a35-909b-d9c781bab09a","name":"B","best_time":"0:02:00.00","average_time":"0:00:00.00"}],"attempts":9,"runs":[{"id":"7de244fb-d7d2-494a-83f7-e287da7484eb","splitFileVersion":1,"totalTime":13186244361,"completed":true,"splitPayloads":[{"split_index":0,"split_segment_id":"0fc8317a-454a-4799-95f9-9bb4eb45a2b6","current_time":"0:00:10.45","current_duration":10450254457},{"split_index":1,"split_segment_id":"ee1f9846-c925-4a35-909b-d9c781bab09a","current_time":"0:00:13.17","current_duration":13170253759}]},{"id":"f9a42348-ecbf-4bdd-9836-f750f078a15d","splitFileVersion":1,"totalTime":0,"completed":false,"splitPayloads":null},{"id":"f9a42348-ecbf-4bdd-9836-f750f078a15d","splitFileVersion":1,"totalTime":0,"completed":false,"splitPayloads":null},{"id":"f70c7e2a-0d2b-4a45-ac67-2a5584c5ec3c","splitFileVersion":1,"totalTime":2848150445,"completed":true,"splitPayloads":[{"split_index":0,"split_segment_id":"0fc8317a-454a-4799-95f9-9bb4eb45a2b6","current_time":"0:00:01.45","current_duration":1454764695},{"split_index":1,"split_segment_id":"ee1f9846-c925-4a35-909b-d9c781bab09a","current_time":"0:00:02.83","current_duration":2834764981}]},{"id":"22747b03-fd61-4c44-9e33-c36cb4d00bed","splitFileVersion":1,"totalTime":0,"completed":false,"splitPayloads":null},{"id":"f1a96f85-f9e3-477a-883f-68285b8b08c1","splitFileVersion":1,"totalTime":5029069465,"completed":true,"splitPayloads":[{"split_index":0,"split_segment_id":"0fc8317a-454a-4799-95f9-9bb4eb45a2b6","current_time":"0:00:02.39","current_duration":2395045999},{"split_index":1,"split_segment_id":"ee1f9846-c925-4a35-909b-d9c781bab09a","current_time":"0:00:05.02","current_duration":5021429184}]},{"id":"e47b283c-fa10-42ef-bb33-de6a34051826","splitFileVersion":1,"totalTime":4298421810,"completed":true,"splitPayloads":[{"split_index":0,"split_segment_id":"0fc8317a-454a-4799-95f9-9bb4eb45a2b6","current_time":"0:00:02.97","current_duration":2979723253},{"split_index":1,"split_segment_id":"ee1f9846-c925-4a35-909b-d9c781bab09a","current_time":"0:00:04.27","current_duration":4279724348}]},{"id":"ca05f580-0fb2-42ba-8c1d-3b1c751f8385","splitFileVersion":1,"totalTime":2274795393,"completed":true,"splitPayloads":[{"split_index":0,"split_segment_id":"0fc8317a-454a-4799-95f9-9bb4eb45a2b6","current_time":"0:00:01.24","current_duration":1242464622},{"split_index":1,"split_segment_id":"ee1f9846-c925-4a35-909b-d9c781bab09a","current_time":"0:00:02.26","current_duration":2262464771}]},{"id":"e1da3ec4-7161-400b-8d66-206ca375a191","splitFileVersion":1,"totalTime":1914949468,"completed":true,"splitPayloads":[{"split_index":0,"split_segment_id":"0fc8317a-454a-4799-95f9-9bb4eb45a2b6","current_time":"0:00:00.84","current_duration":840096903},{"split_index":1,"split_segment_id":"ee1f9846-c925-4a35-909b-d9c781bab09a","current_time":"0:00:01.90","current_duration":1900097400}]},{"id":"3a9acc19-0960-406d-bf49-efcdd14c9c57","splitFileVersion":1,"totalTime":1603571992,"completed":true,"splitPayloads":[{"split_index":0,"split_segment_id":"0fc8317a-454a-4799-95f9-9bb4eb45a2b6","current_time":"0:00:00.87","current_duration":870589603},{"split_index":1,"split_segment_id":"ee1f9846-c925-4a35-909b-d9c781bab09a","current_time":"0:00:01.59","current_duration":1590587830}]}]}
1+
{"id":"dd58a2a7-3474-4fc9-9b80-df1d0795663c","version":0,"attempts":2,"game_name":"Game","game_category":"Category","window_x":10,"window_y":135,"window_height":550,"window_width":350,"runs":[{"id":"f4994c82-cfaf-4bd8-9353-b8ed233c45ff","split_file_version":0,"total_time":13782,"splits":{"3c9c6862-da93-4476-a53e-9d44a48a62d7":{"split_segment_id":"3c9c6862-da93-4476-a53e-9d44a48a62d7","current_cumulative":11562,"current_duration":6140},"797aeacb-0042-4cd0-aaf4-c381ecc05494":{"split_segment_id":"797aeacb-0042-4cd0-aaf4-c381ecc05494","current_cumulative":13782,"current_duration":2220},"d48dfdd8-8bc7-4b4a-a07d-030e048a8e3b":{"split_segment_id":"d48dfdd8-8bc7-4b4a-a07d-030e048a8e3b","current_cumulative":5422,"current_duration":5422}},"leaf_segments":null,"completed":false},{"id":"afcd5acd-2222-483f-bace-46f42dc32992","split_file_version":0,"total_time":5618,"splits":{"3c9c6862-da93-4476-a53e-9d44a48a62d7":{"split_segment_id":"3c9c6862-da93-4476-a53e-9d44a48a62d7","current_cumulative":3298,"current_duration":2120},"797aeacb-0042-4cd0-aaf4-c381ecc05494":{"split_segment_id":"797aeacb-0042-4cd0-aaf4-c381ecc05494","current_cumulative":5618,"current_duration":2320},"d48dfdd8-8bc7-4b4a-a07d-030e048a8e3b":{"split_segment_id":"d48dfdd8-8bc7-4b4a-a07d-030e048a8e3b","current_cumulative":1178,"current_duration":1178}},"leaf_segments":null,"completed":false}],"segments":[{"id":"baddf90c-5638-4216-81e3-396701dfd280","name":"Foo","gold":0,"average":0,"pb":0,"children":[{"id":"ef731019-58f0-4c0b-87b8-297c2ec20223","name":"Sub","gold":0,"average":0,"pb":0,"children":[{"id":"d48dfdd8-8bc7-4b4a-a07d-030e048a8e3b","name":"Another Sub","gold":1178,"average":3300,"pb":1178,"children":[]}]}]},{"id":"3c9c6862-da93-4476-a53e-9d44a48a62d7","name":"Baz","gold":2120,"average":4130,"pb":2120,"children":[]},{"id":"797aeacb-0042-4cd0-aaf4-c381ecc05494","name":"End","gold":2220,"average":2270,"pb":2320,"children":[]}],"sob":5518,"pb":{"id":"afcd5acd-2222-483f-bace-46f42dc32992","split_file_version":0,"total_time":5618,"splits":{"3c9c6862-da93-4476-a53e-9d44a48a62d7":{"split_segment_id":"3c9c6862-da93-4476-a53e-9d44a48a62d7","current_cumulative":3298,"current_duration":2120},"797aeacb-0042-4cd0-aaf4-c381ecc05494":{"split_segment_id":"797aeacb-0042-4cd0-aaf4-c381ecc05494","current_cumulative":5618,"current_duration":2320},"d48dfdd8-8bc7-4b4a-a07d-030e048a8e3b":{"split_segment_id":"d48dfdd8-8bc7-4b4a-a07d-030e048a8e3b","current_cumulative":1178,"current_duration":1178}},"leaf_segments":null,"completed":false},"offset":0,"platform":"GameCube"}

0 commit comments

Comments
 (0)