1
use crate::format::{
2
    GrainAllocationInfo, GrainAllocationStatus, GrainIndex, LocalGrainId, StratumHeader,
3
};
4

            
5
14
#[derive(Debug)]
6
pub struct FreeLocations {
7
    free_locations: Vec<LocationInfo>,
8
    grains_free: u16,
9
}
10

            
11
impl Default for FreeLocations {
12
51
    fn default() -> Self {
13
51
        Self {
14
51
            free_locations: vec![LocationInfo {
15
51
                offset: GrainIndex::new(0).expect("0 is valid"),
16
51
                length: 16_372,
17
51
            }],
18
51
            grains_free: 16_372,
19
51
        }
20
51
    }
21
}
22

            
23
impl FreeLocations {
24
291
    pub fn from_stratum(stratum: &StratumHeader) -> Self {
25
291
        let mut free_locations = Vec::new();
26
291

            
27
291
        let mut index = 0;
28
62629
        while index < 16_372 {
29
62338
            let index_status = stratum.grain_info(index);
30
62338
            let count = index_status.count();
31
62338
            let free = matches!(
32
62338
                index_status.status().expect("invalid header"),
33
                GrainAllocationStatus::Free
34
            );
35

            
36
62338
            if free {
37
307
                // See how many free grains in a row we have.
38
307
                let free_until = if let Some(next_allocated_index) = stratum.grains[index + 1..]
39
307
                    .iter()
40
307
                    .enumerate()
41
307
                    .find_map(|(index, info)| {
42
2724140
                        matches!(
43
2724162
                            GrainAllocationInfo(*info).status().expect("invalid header"),
44
                            GrainAllocationStatus::Allocated | GrainAllocationStatus::Archived
45
                        )
46
2724162
                        .then_some(index)
47
2724162
                    }) {
48
16
                    next_allocated_index + index + 1
49
                } else {
50
291
                    16_372
51
                };
52

            
53
307
                insert_location_info(
54
307
                    &mut free_locations,
55
307
                    LocationInfo {
56
307
                        offset: GrainIndex::new(index as u16).expect("valid index"),
57
307
                        length: (free_until - index) as u16,
58
307
                    },
59
307
                );
60
307

            
61
307
                index = free_until;
62
62031
            } else {
63
62031
                index += usize::from(count);
64
62031
            }
65
        }
66

            
67
307
        let grains_free = free_locations.iter().map(|loc| loc.length).sum::<u16>();
68
291
        Self {
69
291
            free_locations,
70
291
            grains_free,
71
291
        }
72
291
    }
73

            
74
31020
    pub fn allocate(&mut self, number_of_grains: u8) -> Option<LocalGrainId> {
75
31020
        let number_of_grains_u16 = u16::from(number_of_grains);
76
31020
        if number_of_grains_u16 <= self.grains_free {
77
30770
            for (index, location) in self.free_locations.iter().enumerate() {
78
30770
                if location.length >= number_of_grains_u16 {
79
30758
                    self.grains_free -= number_of_grains_u16;
80
30758
                    // This position can be split. If the new length will cause the
81
30758
                    // location to shift position, we'll remove and reinsert it.
82
30758
                    let new_length = location.length - number_of_grains_u16;
83
30758
                    let grain_index = location.offset;
84
30758

            
85
30758
                    if new_length == 0 {
86
17
                        self.free_locations.remove(index);
87
30741
                    } else {
88
30741
                        self.free_locations[index].offset += number_of_grains;
89
30741
                        self.free_locations[index].length = new_length;
90
30741

            
91
30741
                        self.check_if_location_needs_to_move(index);
92
30741
                    }
93

            
94
30758
                    return Some(
95
30758
                        LocalGrainId::from_parts(grain_index, number_of_grains)
96
30758
                            .expect("invalid grain count"),
97
30758
                    );
98
12
                }
99
            }
100
262
        }
101

            
102
262
        None
103
31020
    }
104

            
105
30747
    fn check_if_location_needs_to_move(&mut self, index: usize) {
106
30747
        let info = &self.free_locations[index];
107
30747
        if index > 0 && self.free_locations[index - 1].length > info.length {
108
            // Needs to move below
109
1
            if index == 1 || (index > 1 && self.free_locations[index - 2].length < info.length) {
110
1
                // A simple swap of index and index - 1 is enough
111
1
                self.free_locations.swap(index - 1, index);
112
1
            } else {
113
                // We need to move this row more than a single location
114
                let info = self.free_locations.remove(index);
115
                insert_location_info(&mut self.free_locations, info);
116
            }
117
30746
        } else if index + 1 < self.free_locations.len()
118
12
            && self.free_locations[index + 1].length < info.length
119
        {
120
            // Needs to move above
121
            if index + 2 == self.free_locations.len()
122
                || (index + 2 < self.free_locations.len()
123
                    && self.free_locations[index + 1].length > info.length)
124
            {
125
                // A simple swap of index and index - 1 is enough
126
                self.free_locations.swap(index, index + 1);
127
            } else {
128
                // We need to move this row more than a single location
129
                let info = self.free_locations.remove(index);
130
                insert_location_info(&mut self.free_locations, info);
131
            }
132
30746
        }
133
30747
    }
134

            
135
    #[must_use]
136
7
    pub fn allocate_grain(&mut self, grain_id: LocalGrainId) -> bool {
137
7
        let start = grain_id.grain_index();
138
7
        let count = u16::from(grain_id.grain_count());
139
7
        let end = start.as_u16() + count;
140

            
141
7
        for (index, info) in self.free_locations.iter_mut().enumerate() {
142
7
            let location_end = info.offset.as_u16() + info.length;
143
7
            if location_end >= start.as_u16() && info.offset.as_u16() <= end {
144
7
                if info.offset == start {
145
                    // Allocate from the start
146
6
                    info.offset = GrainIndex::new(end).expect("valid index");
147
6
                    info.length -= count;
148
6
                    if info.length > 0 {
149
5
                        self.check_if_location_needs_to_move(index);
150
5
                    } else {
151
1
                        // The location is now empty.
152
1
                        self.free_locations.remove(index);
153
1
                    }
154
1
                } else if location_end == end {
155
                    // Allocate from the end
156
                    info.length -= count;
157

            
158
                    // We can assume a non-zero length because otherwise
159
                    // info.offset would have been equal to start.
160
                    self.check_if_location_needs_to_move(index);
161
1
                } else {
162
1
                    // Split this into two
163
1
                    let remaining_start_grains = start.as_u16() - info.offset.as_u16();
164
1
                    let remaining_end_grains = location_end - end;
165
1

            
166
1
                    info.length = remaining_start_grains;
167
1
                    self.check_if_location_needs_to_move(index);
168
1

            
169
1
                    // Add the new region for the tail end of the split.
170
1
                    insert_location_info(
171
1
                        &mut self.free_locations,
172
1
                        LocationInfo {
173
1
                            offset: GrainIndex::new(end).expect("valid index"),
174
1
                            length: remaining_end_grains,
175
1
                        },
176
1
                    );
177
1
                }
178
7
                self.grains_free -= count;
179
7
                return true;
180
            }
181
        }
182

            
183
        false
184
7
    }
185

            
186
1128
    pub fn free_grain(&mut self, grain_id: LocalGrainId) {
187
1128
        let start = grain_id.grain_index();
188
1128
        let count = u16::from(grain_id.grain_count());
189
1128
        let end = start.as_u16() + count;
190
1128

            
191
1128
        self.grains_free += count;
192

            
193
        // First, attempt to find a grain whose end matches the start or whose
194
        // start matches the end.
195
141162
        for (index, info) in self.free_locations.iter_mut().enumerate() {
196
141162
            let location_end = info.offset.as_u16() + info.length;
197
141162
            // Check for any overlap of the two regions
198
141162
            if info.offset.as_u16() <= end && location_end >= start.as_u16() {
199
                // If we don't match the end or start, this is an invalid
200
                // operation.
201
28
                if info.offset.as_u16() == end {
202
4
                    // Prepend the free grains
203
4
                    info.offset = start;
204
4
                    info.length += count;
205
4
                    self.scan_for_merge_with_start(index, start.as_u16());
206
24
                } else if location_end == start.as_u16() {
207
24
                    // Append the free grains
208
24
                    info.length += count;
209
24
                    self.scan_for_merge_with_end(index, end);
210
24
                } else {
211
                    unreachable!("invalid free operation: grain was already partially free")
212
                }
213

            
214
28
                return;
215
141134
            }
216
        }
217

            
218
        // If we couldn't find an existing location to latch onto, we need to
219
        // insert it.
220
1100
        insert_location_info(
221
1100
            &mut self.free_locations,
222
1100
            LocationInfo {
223
1100
                offset: start,
224
1100
                length: count,
225
1100
            },
226
1100
        )
227
1128
    }
228

            
229
4
    fn scan_for_merge_with_start(&mut self, possible_merge_index: usize, start: u16) {
230
4
        for index in possible_merge_index + 1..self.free_locations.len() {
231
            let info = &self.free_locations[index];
232

            
233
            if info.offset.as_u16() + info.length == start {
234
                let new_start = info.offset;
235
                let count = info.length;
236
                self.free_locations.remove(index);
237
                self.free_locations[possible_merge_index].offset = new_start;
238
                self.free_locations[possible_merge_index].length += count;
239
                return;
240
            }
241
        }
242
4
    }
243

            
244
24
    fn scan_for_merge_with_end(&mut self, possible_merge_index: usize, end: u16) {
245
651
        for index in possible_merge_index + 1..self.free_locations.len() {
246
651
            let info = &self.free_locations[index];
247
651

            
248
651
            if info.offset.as_u16() == end {
249
3
                let count = info.length;
250
3
                self.free_locations.remove(index);
251
3
                self.free_locations[possible_merge_index].length += count;
252
3
                return;
253
648
            }
254
        }
255
24
    }
256

            
257
553
    pub const fn is_full(&self) -> bool {
258
553
        self.grains_free == 0
259
553
    }
260
}
261

            
262
1408
fn insert_location_info(free_locations: &mut Vec<LocationInfo>, info: LocationInfo) {
263
1408
    let insert_at = free_locations
264
2068
        .binary_search_by(|loc| loc.length.cmp(&info.length))
265
1408
        .map_or_else(|a| a, |a| a);
266
1408
    free_locations.insert(insert_at, info);
267
1408
}
268

            
269
21
#[derive(Debug)]
270
struct LocationInfo {
271
    offset: GrainIndex,
272
    length: u16,
273
}
274

            
275
1
#[test]
276
1
fn basics() {
277
1
    let mut allocator = FreeLocations::default();
278
1
    let first = allocator.allocate(16).expect("failed to allocate");
279
1
    println!("Allocated {first}: {allocator:?}");
280
1
    assert_eq!(allocator.free_locations.len(), 1);
281
1
    let second = allocator.allocate(16).expect("failed to allocate");
282
1
    println!("Allocated {second}: {allocator:?}");
283
1
    assert_eq!(allocator.free_locations.len(), 1);
284
1
    allocator.free_grain(first);
285
1
    println!("Freed {first}: {allocator:?}");
286
1
    assert_eq!(allocator.free_locations.len(), 2);
287
1
    let reused_a = allocator.allocate(8).expect("failed to allocate");
288
1
    println!("Allocated {reused_a}: {allocator:?}");
289
1
    assert_eq!(allocator.free_locations.len(), 2);
290
1
    let reused_b = allocator.allocate(8).expect("failed to allocate");
291
1
    println!("Allocated {reused_b}: {allocator:?}");
292
1
    assert_eq!(allocator.free_locations.len(), 1);
293
1
    assert_eq!(allocator.grains_free, 16_372 - 32);
294

            
295
    // Free the grains in order such that the second free ends up joining the
296
    // two existing nodes into one.
297
1
    allocator.free_grain(reused_b);
298
1
    println!("Freed {reused_b}: {allocator:?}");
299
1
    assert_eq!(allocator.free_locations.len(), 2);
300
1
    allocator.free_grain(second);
301
1
    println!("Freed {second}: {allocator:?}");
302
1
    assert_eq!(allocator.free_locations.len(), 1);
303
1
    allocator.free_grain(reused_a);
304
1
    println!("Freed {reused_a}: {allocator:?}");
305
1
    assert_eq!(allocator.grains_free, 16_372);
306

            
307
    // Re-allocate using these grain ids
308
1
    assert!(allocator.allocate_grain(second));
309
1
    println!("Allocated {second}: {allocator:?}");
310
1
    assert_eq!(allocator.free_locations.len(), 2);
311
1
    assert!(allocator.allocate_grain(reused_a));
312
1
    println!("Allocated {reused_a}: {allocator:?}");
313
1
    assert_eq!(allocator.free_locations.len(), 2);
314
1
    assert!(allocator.allocate_grain(reused_b));
315
1
    println!("Allocated {reused_b}: {allocator:?}");
316
1
    assert_eq!(allocator.free_locations.len(), 1);
317
1
    assert_eq!(allocator.grains_free, 16_372 - 32);
318

            
319
    // Free in a different order this time.
320
1
    allocator.free_grain(reused_a);
321
1
    println!("Freed {reused_a}: {allocator:?}");
322
1
    assert_eq!(allocator.free_locations.len(), 2);
323
1
    allocator.free_grain(second);
324
1
    println!("Freed {second}: {allocator:?}");
325
1
    assert_eq!(allocator.free_locations.len(), 2);
326
1
    allocator.free_grain(reused_b);
327
1
    println!("Freed {reused_b}: {allocator:?}");
328
1
    assert_eq!(allocator.free_locations.len(), 1);
329
1
    assert_eq!(allocator.grains_free, 16_372);
330
1
}