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}