Skip to main content

unlit_ecs/
borrow.rs

1//! The ledger of who holds a component.
2//!
3//! Component values live in `UnsafeCell`s, so the compiler cannot police
4//! aliasing between two access paths, and this ledger does. A claim is
5//! registered before any reference to a value is handed out and is released when
6//! the holder goes away; a claim that overlaps one already held panics, naming
7//! both sites. Nothing else checks aliasing at run time.
8//!
9//! A claim covers either every row of one archetype, which is what a query over a
10//! scope asks for, or one row, which is what a single-entity lookup asks for.
11//! Two claims conflict when they name the same component type, at least one of
12//! them writes, and they can reach the same row: two single-row claims when they
13//! name the same entity, anything else when they name the same archetype.
14//!
15//! The archetype granularity is deliberately conservative. A query cannot tell
16//! before iterating which rows of a mixed archetype a scope can see, so it claims
17//! the whole archetype; a single-entity claim on a row of that archetype is then
18//! refused even when the query would not have reached that row. Refusing a pair
19//! that would have been fine is the price of never allowing a pair that is not.
20//!
21//! Claims are filed under the holder that made them rather than under individual
22//! ids, so a holder carries one word of ledger state and releases in one pass.
23//!
24//! What a holder claims is what it asked a [`crate::PlanBuilder`] for: there is
25//! no separate declaration of reached components, so a query cannot claim less
26//! than it reads.
27
28use core::any::TypeId;
29use core::panic::Location;
30
31use crate::entity::Entity;
32use crate::query::{Ask, Mode};
33use crate::world::World;
34
35/// Which rows a claim covers.
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub(crate) enum Rows {
38    /// Every row of one archetype.
39    Archetype(u32),
40    /// The one row one entity lives at.
41    Entity { archetype: u32, entity: Entity },
42}
43
44impl Rows {
45    fn archetype(self) -> u32 {
46        match self {
47            Self::Archetype(archetype) => archetype,
48            Self::Entity { archetype, .. } => archetype,
49        }
50    }
51
52    /// Whether two claims can reach the same row.
53    fn overlaps(self, other: Self) -> bool {
54        match (self, other) {
55            (Self::Entity { entity: left, .. }, Self::Entity { entity: right, .. }) => {
56                left == right
57            }
58            _ => self.archetype() == other.archetype(),
59        }
60    }
61}
62
63/// One registered claim.
64struct Lease {
65    /// The holder this claim belongs to, so releasing is exact.
66    group: u64,
67    type_id: TypeId,
68    name: &'static str,
69    mode: Mode,
70    rows: Rows,
71    /// Where the claim was made, reported when a later claim conflicts with it.
72    origin: &'static Location<'static>,
73}
74
75impl Lease {
76    /// The panic for a claim that overlaps this one.
77    fn conflict(&self, mode: Mode, origin: &'static Location<'static>) -> ! {
78        let held = self.mode.adverb();
79        let wanted = mode.verb();
80        panic!(
81            "component `{}` is already borrowed {held} at {}, and this {wanted} at {origin} \
82             cannot share it",
83            self.name, self.origin,
84        )
85    }
86}
87
88/// Every claim currently held on a world's components.
89#[derive(Default)]
90pub(crate) struct BorrowLedger {
91    /// The id the next holder is given.
92    next: u64,
93    held: Vec<Lease>,
94}
95
96impl BorrowLedger {
97    /// The id the next holder files its claims under.
98    pub(crate) fn next_group(&mut self) -> u64 {
99        let group = self.next;
100        self.next += 1;
101        group
102    }
103
104    /// Register a claim, or panic when it overlaps one held by another holder.
105    ///
106    /// A holder asking again for a component it already holds over the same rows
107    /// is not a conflict: that is what a second pass over one archetype does.
108    pub(crate) fn claim(
109        &mut self,
110        group: u64,
111        type_id: TypeId,
112        name: &'static str,
113        mode: Mode,
114        rows: Rows,
115        origin: &'static Location<'static>,
116    ) {
117        for lease in &self.held {
118            if lease.type_id != type_id || !lease.rows.overlaps(rows) {
119                continue;
120            }
121            if lease.group == group && lease.rows == rows && lease.mode == mode {
122                return;
123            }
124            if lease.mode == Mode::Write || mode == Mode::Write {
125                lease.conflict(mode, origin);
126            }
127        }
128        self.held.push(Lease {
129            group,
130            type_id,
131            name,
132            mode,
133            rows,
134            origin,
135        });
136    }
137
138    /// Drop every claim one holder made.
139    pub(crate) fn release(&mut self, group: u64) {
140        self.held.retain(|lease| lease.group != group);
141    }
142}
143
144/// The claims one holder owns, released together when it is dropped.
145///
146/// A holder keeps its claims for its whole life rather than for one pass over
147/// the rows: an item it handed out borrows from it, so a claim released between
148/// two items would let another holder reach a value an item still points at.
149pub(crate) struct Claims<'w> {
150    world: &'w World,
151    /// What every claim this holder makes is filed under.
152    group: u64,
153}
154
155impl<'w> Claims<'w> {
156    pub(crate) fn new(world: &'w World) -> Self {
157        Self {
158            world,
159            group: world.next_group(),
160        }
161    }
162
163    /// Claim on `index` every component the holder's asks named.
164    pub(crate) fn claim_archetype(
165        &mut self,
166        index: u32,
167        archetype: &crate::archetype::Archetype,
168        asks: &[Ask],
169        origin: &'static Location<'static>,
170    ) {
171        claim_archetype(self.world, self.group, index, archetype, asks, origin);
172    }
173
174    /// Claim on the row of `entity` every component the holder's asks named.
175    pub(crate) fn claim_row(
176        &mut self,
177        index: u32,
178        entity: Entity,
179        archetype: &crate::archetype::Archetype,
180        asks: &[Ask],
181        origin: &'static Location<'static>,
182    ) {
183        claim_row(
184            self.world, self.group, index, entity, archetype, asks, origin,
185        );
186    }
187}
188
189impl Drop for Claims<'_> {
190    fn drop(&mut self) {
191        self.world.release(self.group);
192    }
193}
194
195/// Claim on `archetype` every component the asks named, shared or exclusive.
196///
197/// Components the archetype does not carry are skipped: an optional item reaches
198/// nothing there, and claiming it anyway would refuse another query for no
199/// reason.
200pub(crate) fn claim_archetype(
201    world: &World,
202    group: u64,
203    index: u32,
204    archetype: &crate::archetype::Archetype,
205    asks: &[Ask],
206    origin: &'static Location<'static>,
207) {
208    for &(type_id, name, mode) in asks {
209        if archetype.column_index(type_id).is_some() {
210            world.claim(group, type_id, name, mode, Rows::Archetype(index), origin);
211        }
212    }
213}
214
215/// Claim on one entity's row every component the asks named.
216pub(crate) fn claim_row(
217    world: &World,
218    group: u64,
219    index: u32,
220    entity: Entity,
221    archetype: &crate::archetype::Archetype,
222    asks: &[Ask],
223    origin: &'static Location<'static>,
224) {
225    for &(type_id, name, mode) in asks {
226        if archetype.column_index(type_id).is_some() {
227            let rows = Rows::Entity {
228                archetype: index,
229                entity,
230            };
231            world.claim(group, type_id, name, mode, rows, origin);
232        }
233    }
234}