33// https://github.com/ZellyDev-Games/OpenSplit
44
55use crate :: { Run , Segment , Time , TimeSpan , platform:: prelude:: * } ;
6- use alloc:: borrow:: Cow ;
6+ use alloc:: { borrow:: Cow , collections :: BTreeMap } ;
77use core:: result:: Result as StdResult ;
88use serde_derive:: Deserialize ;
99use 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" ) ]
4045struct 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
6771fn 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)
0 commit comments