edf_rs/save.rs
1use crate::headers::signal_header::SignalHeader;
2use crate::record::Record;
3
4pub fn normalize_instructions(
5 instructions: &Vec<SaveInstruction>,
6 initial_count: usize,
7) -> Vec<SaveInstruction> {
8 let mut normalized_instructions = Vec::new();
9
10 // Counter keeping track of the amount of elements after the current instruction.
11 // This is only required to be able to translate APPEND instructions into an INSERT
12 // instruction with the given index
13 let mut item_counter = initial_count;
14
15 // Go through every instruction and normalize it and add it to the final list of
16 // normalized instructions and remove/ignore instructions cancelling out each other
17 for tr in instructions {
18 // Turn an APPEND instruction into an INSERT instruction
19 let mut instruction = if let SaveInstruction::Append(c) = tr {
20 SaveInstruction::Insert(item_counter, c.clone())
21 } else {
22 tr.clone()
23 };
24
25 // Adjust previous instructions to new states
26 let mut add_instruction = true;
27 match instruction {
28 SaveInstruction::Insert(current_idx, _) => {
29 for current in normalized_instructions.iter_mut().rev() {
30 match current {
31 SaveInstruction::Insert(idx, _) | SaveInstruction::Update(idx, _)
32 if *idx >= current_idx =>
33 {
34 *idx += 1
35 }
36
37 // Delete needs to be larger than and not just equal as otherwise
38 // the instruction would immediately delete the inserted value
39 SaveInstruction::Remove(idx) if *idx > current_idx => *idx += 1,
40
41 // Any other unaffected operations
42 _ => {}
43 };
44 }
45
46 item_counter += 1;
47 }
48 SaveInstruction::Update(current_idx, ref c) => {
49 // If there was an INSERT instruction before which would be updated by the current UPDATE instruction,
50 // replace the initial INSERT and the current UPDATE instructions with a single INSERT instruction.
51 if let Some(idx) = normalized_instructions.iter().position(
52 |tr| matches!(tr, SaveInstruction::Insert(idx, _) if *idx == current_idx),
53 ) {
54 normalized_instructions.remove(idx);
55 instruction = SaveInstruction::Insert(current_idx, c.clone());
56 }
57 // Replace a previous UPDATE instruction which would be replaced by the current UPDATE instruction with the current one
58 else if let Some(idx) = normalized_instructions.iter().position(
59 |tr| matches!(tr, SaveInstruction::Update(idx, _) if *idx == current_idx),
60 ) {
61 normalized_instructions.remove(idx);
62 };
63 }
64 SaveInstruction::Remove(current_idx) => {
65 let mut idx_eliminate = -1;
66 for (i, current) in normalized_instructions.iter_mut().enumerate().rev() {
67 match current {
68 // Any instructions with index after delete
69 SaveInstruction::Insert(idx, _)
70 | SaveInstruction::Update(idx, _)
71 | SaveInstruction::Remove(idx)
72 if *idx > current_idx =>
73 {
74 *idx -= 1
75 }
76
77 // Insert and delete cancel each other out
78 SaveInstruction::Insert(idx, _)
79 if *idx == current_idx && idx_eliminate == -1 =>
80 {
81 idx_eliminate = i as i64;
82 add_instruction = false;
83 }
84
85 // Update cancelled out by delete
86 SaveInstruction::Update(idx, _)
87 if *idx == current_idx && idx_eliminate == -1 =>
88 {
89 idx_eliminate = i as i64
90 }
91
92 // Any other unaffected operations
93 _ => {}
94 };
95 }
96
97 // If any, remove the instruction which will become useless due to this delete instruction.
98 // E.g. When there is an INSERT at index 3 and this is a DELETE at index 3 instruction. Therefore
99 // both instructions cancel each other out. An UPDATE followed by a DELETE would cause the UPDATE to
100 // have no effect, as the DELETE would remove the item at that index anyways
101 if idx_eliminate >= 0 {
102 normalized_instructions.remove(idx_eliminate as usize);
103 }
104
105 item_counter -= 1;
106 }
107 SaveInstruction::WriteHeader => {}
108
109 // Convenience operations which are not supposed to be in the final instruction list
110 // should not be handled (e.g. APPEND will always turn into INSERT and therefore does not
111 // require any handling)
112 _ => {
113 add_instruction = false;
114 }
115 };
116
117 // Add the instruction to the final list in case it did not get cancelled out with another operation (
118 // this would only be the case when an INSERT instruction was deleted again)
119 if add_instruction {
120 normalized_instructions.push(instruction);
121 }
122 }
123
124 // Sort instructions by index and by their priority (equal indices sort by their instruction type in the order of [DELETE; INSERT; UPDATE])
125 // to make all indices valid and to be able to work into a single direction
126 normalized_instructions.sort_by(|a, b| {
127 a.index()
128 .cmp(&b.index())
129 .then_with(|| a.priority().cmp(&b.priority()))
130 });
131
132 // Merge DELETE instructions followed by an INSERT instruction with both having the same index into a single UPDATE instruction
133 merge_to_updates(normalized_instructions)
134}
135
136/// Merges a delete instruction immediately followed by an insert instruction where both are targeting
137/// the same index into a single Update instruction.
138fn merge_to_updates(instructions: Vec<SaveInstruction>) -> Vec<SaveInstruction> {
139 let mut out = Vec::with_capacity(instructions.len());
140 let mut iter = instructions.into_iter().peekable();
141
142 while let Some(curr) = iter.next() {
143 match (&curr, iter.peek()) {
144 (SaveInstruction::Remove(idx_d), Some(SaveInstruction::Insert(idx_i, c)))
145 if idx_d == idx_i =>
146 {
147 out.push(SaveInstruction::Update(*idx_d, c.clone()));
148 iter.next();
149 }
150 _ => out.push(curr),
151 }
152 }
153
154 out
155}
156
157#[derive(Debug, Clone, PartialEq)]
158pub enum SaveValue {
159 Record(Record),
160 Signal(SignalHeader),
161}
162
163#[derive(Debug, Clone, PartialEq)]
164pub enum SaveInstruction {
165 WriteHeader,
166 Update(usize, SaveValue),
167 Insert(usize, SaveValue),
168 Append(SaveValue),
169 Remove(usize),
170 Patch,
171}
172
173impl SaveInstruction {
174 pub fn index(&self) -> usize {
175 match self {
176 SaveInstruction::WriteHeader => 0,
177 SaveInstruction::Remove(idx)
178 | SaveInstruction::Insert(idx, _)
179 | SaveInstruction::Update(idx, _) => *idx,
180 _ => usize::MAX,
181 }
182 }
183
184 pub fn priority(&self) -> u8 {
185 match self {
186 SaveInstruction::WriteHeader => 0,
187 SaveInstruction::Remove(_) => 1,
188 SaveInstruction::Insert(_, _) => 2,
189 SaveInstruction::Update(_, _) => 3,
190 _ => u8::MAX,
191 }
192 }
193
194 pub fn has_record_index(&self) -> bool {
195 match self {
196 SaveInstruction::Remove(_)
197 | SaveInstruction::Insert(_, _)
198 | SaveInstruction::Update(_, _) => true,
199 _ => false,
200 }
201 }
202
203 pub fn record_index(&self) -> usize {
204 if !self.has_record_index() {
205 return usize::MAX;
206 }
207
208 match self {
209 SaveInstruction::Remove(idx)
210 | SaveInstruction::Insert(idx, _)
211 | SaveInstruction::Update(idx, _) => *idx,
212 _ => usize::MAX,
213 }
214 }
215}