Skip to main content

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}