id_alloc/
lib.rs

1// SPDX-License-Identifier: MPL-2.0
2
3#![cfg_attr(not(test), no_std)]
4#![deny(unsafe_code)]
5
6use core::{fmt::Debug, ops::Range};
7
8use bitvec::prelude::BitVec;
9
10/// An id allocator implemented by the bitmap.
11/// The true bit implies that the id is allocated, and vice versa.
12#[derive(Clone)]
13pub struct IdAlloc {
14    bitset: BitVec<u8>,
15    first_available_id: usize,
16}
17
18impl IdAlloc {
19    /// Constructs a new id allocator with a maximum capacity.
20    pub fn with_capacity(capacity: usize) -> Self {
21        let mut bitset = BitVec::with_capacity(capacity);
22        bitset.resize(capacity, false);
23        Self {
24            bitset,
25            first_available_id: 0,
26        }
27    }
28
29    /// Constructs a new id allocator from a slice of `u8` bytes and a maximum capacity.
30    ///
31    /// The slice of `u8` bytes is the raw data of a bitmap.
32    pub fn from_bytes_with_capacity(slice: &[u8], capacity: usize) -> Self {
33        let bitset = if capacity > slice.len() * 8 {
34            let mut bitset = BitVec::from_slice(slice);
35            bitset.resize(capacity, false);
36            bitset
37        } else {
38            let mut bitset = BitVec::from_slice(&slice[..capacity.div_ceil(8)]);
39            bitset.truncate(capacity);
40            bitset
41        };
42
43        let first_available_id = (0..bitset.len())
44            .find(|&i| !bitset[i])
45            .map_or(bitset.len(), |i| i);
46
47        Self {
48            bitset,
49            first_available_id,
50        }
51    }
52
53    /// Allocates and returns a new `id`.
54    ///
55    /// If allocation is not possible, it returns `None`.
56    pub fn alloc(&mut self) -> Option<usize> {
57        if self.first_available_id < self.bitset.len() {
58            let id = self.first_available_id;
59            self.bitset.set(id, true);
60            self.first_available_id = (id + 1..self.bitset.len())
61                .find(|&i| !self.bitset[i])
62                .map_or(self.bitset.len(), |i| i);
63            Some(id)
64        } else {
65            None
66        }
67    }
68
69    /// Allocates a consecutive range of new `id`s.
70    ///
71    /// The `count` is the number of consecutive `id`s to allocate. If it is 0, return `None`.
72    ///
73    /// If allocation is not possible, it returns `None`.
74    ///
75    /// TODO: Choose a more efficient strategy.
76    pub fn alloc_consecutive(&mut self, count: usize) -> Option<Range<usize>> {
77        if count == 0 {
78            return None;
79        }
80
81        // Scan the bitmap from the position `first_available_id`
82        // for the first `count` number of consecutive 0's.
83        let allocated_range = {
84            // Invariance: all bits within `curr_range` are 0's
85            let mut curr_range = self.first_available_id..self.first_available_id + 1;
86            while curr_range.len() < count && curr_range.end < self.bitset.len() {
87                if !self.is_allocated(curr_range.end) {
88                    curr_range.end += 1;
89                } else {
90                    curr_range = curr_range.end + 1..curr_range.end + 1;
91                }
92            }
93
94            if curr_range.len() < count {
95                return None;
96            }
97
98            curr_range
99        };
100
101        // Set every bit to 1 within the allocated range
102        for id in allocated_range.clone() {
103            self.bitset.set(id, true);
104        }
105
106        // In case we need to update first_available_id
107        if self.is_allocated(self.first_available_id) {
108            self.first_available_id = (allocated_range.end..self.bitset.len())
109                .find(|&i| !self.bitset[i])
110                .map_or(self.bitset.len(), |i| i);
111        }
112
113        Some(allocated_range)
114    }
115
116    /// Releases the consecutive range of allocated `id`s.
117    ///
118    /// # Panics
119    ///
120    /// If the `range` is out of bounds, this method will panic.
121    pub fn free_consecutive(&mut self, range: Range<usize>) {
122        if range.is_empty() {
123            return;
124        }
125
126        let range_start = range.start;
127        for id in range {
128            debug_assert!(self.is_allocated(id));
129            self.bitset.set(id, false);
130        }
131
132        if range_start < self.first_available_id {
133            self.first_available_id = range_start
134        }
135    }
136
137    /// Releases the allocated `id`.
138    ///
139    /// # Panics
140    ///
141    /// If the `id` is out of bounds, this method will panic.
142    pub fn free(&mut self, id: usize) {
143        debug_assert!(self.is_allocated(id));
144
145        self.bitset.set(id, false);
146        if id < self.first_available_id {
147            self.first_available_id = id;
148        }
149    }
150
151    /// Allocate a specific ID.
152    ///
153    /// If the ID is already allocated, it returns `None`, otherwise it
154    /// returns the allocated ID.
155    ///
156    /// # Panics
157    ///
158    /// If the `id` is out of bounds, this method will panic.
159    pub fn alloc_specific(&mut self, id: usize) -> Option<usize> {
160        if self.bitset[id] {
161            return None;
162        }
163        self.bitset.set(id, true);
164        if id == self.first_available_id {
165            self.first_available_id = (id + 1..self.bitset.len())
166                .find(|&i| !self.bitset[i])
167                .map_or(self.bitset.len(), |i| i);
168        }
169        Some(id)
170    }
171
172    /// Returns true if the `id` is allocated.
173    ///
174    /// # Panics
175    ///
176    /// If the `id` is out of bounds, this method will panic.
177    pub fn is_allocated(&self, id: usize) -> bool {
178        self.bitset[id]
179    }
180
181    /// Views the id allocator as a slice of `u8` bytes.
182    pub fn as_bytes(&self) -> &[u8] {
183        self.bitset.as_raw_slice()
184    }
185}
186
187impl Debug for IdAlloc {
188    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
189        f.debug_struct("IdAlloc")
190            .field("len", &self.bitset.len())
191            .field("first_available_id", &self.first_available_id)
192            .finish()
193    }
194}