Option<Position>
needs to store the state (Some
or None
) somewhere, and because Position
already contains 12 bytes of information, you need more space to store it. Usually this means that it adds an extra byte (plus padding) to store the state, although in some cases where the inner type has a known unused state. For example, a reference can point to address 0
, so Option<&'_ T>
could use 0
as the None
state and take up the same number of bytes as &'_ T
. For your Position
type, however, that's not the case.
If you absolutely need your PoolItem
struct to be as small as possible, and if you can spare one bit from your entity_id
field (say, the highest bit, 231), you can use that to store the state instead:
const COMPONENT_USED_BIT: u32 = (1u32 << 31);
struct PoolItem {
entity_id: u32, // lowest 31 bits = entity ID, highest bit = "component used"
component: Position,
}
This might become a bit complex, since you need to ensure that you're treating that bit specially, but you can write a couple of simple accessor methods to ensure that the special bit is dealt with correctly.
impl PoolItem {
/// Get entity ID, without the "component used" bit
fn entity_id(&self) -> u32 {
self.entity_id & !COMPONENT_USED_BIT
}
/// Set entity ID, keeping the existing "component used" bit
fn set_entity_id(&mut self, entity_id: u32) {
let component_used_bit = self.entity_id & COMPONENT_USED_BIT;
self.entity_id = (entity_id & !COMPONENT_USED_BIT) | component_used_bit;
}
/// Get component if "component used" bit is set
fn component(&self) -> Option<&Position> {
if self.entity_id & COMPONENT_USED_BIT != 0 {
Some(&self.component)
} else {
None
}
}
/// Set component, updating the "component used" bit
fn set_component(&mut self, component: Option<Position>) {
if let Some(component) = component {
self.component = component;
self.entity_id |= COMPONENT_USED_BIT;
} else {
self.entity_id &= !COMPONENT_USED_BIT;
}
}
}
Playground example with tests