1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
use super::*;
impl World {
/// Adds an entity in `archetype` without initializing component columns.
///
/// # Safety
///
/// The caller must initialize every component column before the entity can
/// be observed, migrated, removed, or dropped.
pub(crate) unsafe fn add_entity(&mut self, archetype: Archetype) -> EntityId {
let data_index = self.ensure_data_index(archetype);
let entity = self.allocate_entity();
self.bump_storage_epoch();
let location = unsafe { self.data[data_index].add_entity(entity) };
self.set_entity_location(
entity,
EntityLocation {
data_index,
chunk_index: location.chunk_index,
entity_index: location.entity_index,
},
);
self.live_entity_count += 1;
entity
}
/// Spawns a new entity with the given component bundle.
///
/// Returns the [`EntityId`] of the newly created entity. The bundle
/// type is typically a tuple of components:
///
/// ```
/// # use sky_ecs::World;
/// # #[derive(Clone, Copy)] struct Pos { x: f32, y: f32 }
/// # #[derive(Clone, Copy)] struct Vel { x: f32, y: f32 }
/// # let mut world = World::new();
/// let entity = world.spawn((Pos { x: 0.0, y: 0.0 }, Vel { x: 1.0, y: 2.0 }));
/// ```
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityId {
let (archetype, columns) = B::cached_meta();
let data_index = self.ensure_data_index(archetype);
let entity = self.allocate_entity();
self.bump_storage_epoch();
let location = unsafe { self.data[data_index].add_entity(entity) };
self.set_entity_location(
entity,
EntityLocation {
data_index,
chunk_index: location.chunk_index,
entity_index: location.entity_index,
},
);
let chunk = &mut self.data[data_index].chunks[location.chunk_index];
unsafe {
bundle.write_fast(chunk, location.entity_index, columns);
}
self.live_entity_count += 1;
entity
}
/// Spawns multiple entities from an iterator of bundles.
///
/// More efficient than calling [`spawn`](Self::spawn) in a loop because
/// the archetype lookup and entity record allocation are amortised.
pub fn spawn_batch<B: Bundle>(&mut self, bundles: impl IntoIterator<Item = B>) {
let mut iter = bundles.into_iter();
let (lower, upper) = iter.size_hint();
let Some(first) = iter.next() else {
return;
};
let (archetype, columns) = B::cached_meta();
let data_index = self.ensure_data_index(archetype);
self.bump_storage_epoch();
let batch_size = upper.filter(|&upper| upper == lower).unwrap_or(lower);
// The size hint was captured before taking `first`, so `lower` covers
// the whole batch. Reused slots do not append entity records, while
// `Vec::reserve` takes an additional count relative to the current len.
let additional_records = lower.saturating_sub(self.free_entities.len());
if additional_records > 0 {
self.entities.reserve(additional_records);
}
self.data[data_index].prepare_batch_capacity(batch_size);
for bundle in std::iter::once(first).chain(iter) {
let entity = self.allocate_entity();
let location = unsafe { self.data[data_index].add_entity(entity) };
self.set_entity_location(
entity,
EntityLocation {
data_index,
chunk_index: location.chunk_index,
entity_index: location.entity_index,
},
);
let chunk = &mut self.data[data_index].chunks[location.chunk_index];
unsafe {
bundle.write_fast(chunk, location.entity_index, columns);
}
self.live_entity_count += 1;
}
}
pub(crate) fn spawn_dynamic_values(
&mut self,
values: &mut [crate::ecs::dynamic::ErasedComponentValue],
) -> Result<EntityId, crate::ecs::dynamic::DynamicSpawnError> {
for (index, value) in values.iter().enumerate() {
for other in &values[(index + 1)..] {
if value.component.id() == other.component.id() {
return Err(crate::ecs::dynamic::DynamicSpawnError::DuplicateComponent {
component: value.component,
});
}
}
}
let mut builder = create_archetype();
for value in values.iter() {
builder = builder.add_component(value.component);
}
let archetype = builder.build();
let entity = unsafe { self.add_entity(archetype) };
let location = self
.entity_location(entity)
.expect("fresh dynamic entity must have a location");
let chunk = &mut self.data[location.data_index].chunks[location.chunk_index];
for value in values {
let component_index = archetype
.query_component_index(&value.component)
.expect("dynamic component must exist in target archetype");
let ptr = unsafe {
chunk
.column_ptr(component_index)
.add(location.entity_index * value.component.size)
};
value.value.write(ptr);
}
Ok(entity)
}
/// Returns `true` if `entity` is alive in this world.
pub fn contains(&self, entity: EntityId) -> bool {
self.entity_location(entity).is_some()
}
/// Iterates all live entities in dense storage order.
///
/// Entity IDs are runtime-local handles. Systems that need persistent
/// document identity should store their own stable component alongside
/// these IDs.
pub fn entities(&self) -> impl Iterator<Item = EntityId> + '_ {
self.data
.iter()
.flat_map(|data| data.chunks.iter())
.flat_map(|chunk| chunk.entities().iter().copied())
}
/// Returns `true` if `entity` is alive and has component `T`.
#[inline(always)]
pub fn has<T: 'static>(&self, entity: EntityId) -> bool {
let Some(location) = self.entity_location(entity) else {
return false;
};
self.data[location.data_index]
.archetype
.has_component(&component_type::<T>())
}
/// Destroys an entity and drops all its components.
///
/// Returns `true` if the entity existed and was removed,
/// or `false` if the entity ID was stale or invalid.
/// If a component destructor panics, removal and location repair finish
/// before that panic resumes.
pub fn despawn(&mut self, entity: EntityId) -> bool {
let Some(location) = self.entity_location(entity) else {
return false;
};
self.bump_storage_epoch();
let mut drop_panic = None;
{
let chunk = &self.data[location.data_index].chunks[location.chunk_index];
// Safety: `location` identifies a live row. Every droppable
// component is consumed exactly once here, and the row is removed
// before a captured panic is resumed.
unsafe {
Self::drop_entity_components_catching(
chunk,
location.entity_index,
&mut drop_panic,
);
}
}
let moved = self.data[location.data_index].remove_entity(ChunkEntityLocation {
chunk_index: location.chunk_index,
entity_index: location.entity_index,
});
let record = &mut self.entities[entity.index() as usize];
record.clear_location();
if let Some(next_generation) = record.generation.checked_add(1) {
record.generation = next_generation;
self.free_entities.push(entity.index());
}
self.live_entity_count -= 1;
if let Some((moved_entity, moved_location)) = moved {
self.set_entity_location(
moved_entity,
EntityLocation {
data_index: location.data_index,
chunk_index: moved_location.chunk_index,
entity_index: moved_location.entity_index,
},
);
}
if let Some(payload) = drop_panic {
std::panic::resume_unwind(payload);
}
true
}
/// Returns a shared reference to component `T` on `entity`.
///
/// Returns `None` if the entity is dead or does not have `T`.
#[inline(always)]
pub fn get<T: 'static>(&self, entity: EntityId) -> Option<&T> {
let location = self.entity_location(entity)?;
let data = &self.data[location.data_index];
let chunk = &data.chunks[location.chunk_index];
let component_index = chunk
.archetype
.query_component_index(&component_type::<T>())?;
Some(unsafe {
let ptr = chunk
.column_ptr(component_index)
.add(location.entity_index * std::mem::size_of::<T>());
&*(ptr as *const T)
})
}
/// Creates a read-only accessor for repeated random access to component `T`.
///
/// Construction resolves `T` once for every archetype and caches typed
/// views of matching chunk columns. This is useful when a hot loop performs
/// many lookups by [`EntityId`]. For an occasional lookup, use
/// [`World::get`] directly.
///
/// The accessor holds a shared borrow of this world, so structural changes
/// cannot occur while it remains in use.
///
/// ```
/// use sky_ecs::World;
///
/// #[derive(Debug, PartialEq)]
/// struct Position(f32, f32);
///
/// let mut world = World::new();
/// let entity = world.spawn((Position(1.0, 2.0),));
/// let positions = world.accessor::<Position>();
///
/// assert_eq!(positions.get(entity), Some(&Position(1.0, 2.0)));
/// ```
///
/// Structural mutation is rejected while the accessor is still used:
///
/// ```compile_fail
/// use sky_ecs::World;
///
/// struct Position(f32, f32);
///
/// let mut world = World::new();
/// let entity = world.spawn((Position(1.0, 2.0),));
/// let positions = world.accessor::<Position>();
/// world.spawn((Position(3.0, 4.0),));
/// let _ = positions.get(entity);
/// ```
#[inline]
pub fn accessor<T: 'static>(&self) -> ComponentAccessor<'_, T> {
ComponentAccessor::new(self)
}
/// Creates an exclusive accessor for repeated random updates to component `T`.
///
/// Like [`World::accessor`], construction resolves matching component
/// columns before the hot loop. The accessor exclusively borrows the world,
/// and each component reference remains tied to one mutable accessor borrow.
///
/// ```
/// use sky_ecs::World;
///
/// struct Position(f32, f32);
///
/// let mut world = World::new();
/// let entity = world.spawn((Position(1.0, 2.0),));
/// {
/// let mut positions = world.accessor_mut::<Position>();
/// positions.get_mut(entity).unwrap().0 += 3.0;
/// }
/// assert_eq!(world.get::<Position>(entity).unwrap().0, 4.0);
/// ```
///
/// Mutable references from the same accessor cannot overlap:
///
/// ```compile_fail
/// use sky_ecs::World;
///
/// struct Position(f32, f32);
///
/// let mut world = World::new();
/// let first = world.spawn((Position(1.0, 2.0),));
/// let second = world.spawn((Position(3.0, 4.0),));
/// let mut positions = world.accessor_mut::<Position>();
/// let first_position = positions.get_mut(first).unwrap();
/// let second_position = positions.get_mut(second).unwrap();
/// first_position.0 += second_position.0;
/// ```
#[inline]
pub fn accessor_mut<T: 'static>(&mut self) -> ComponentAccessorMut<'_, T> {
ComponentAccessorMut::new(self)
}
/// Returns an exclusive reference to component `T` on `entity`.
///
/// Returns `None` if the entity is dead or does not have `T`.
#[inline(always)]
pub fn get_mut<T: 'static>(&mut self, entity: EntityId) -> Option<&mut T> {
let location = self.entity_location(entity)?;
let data = &mut self.data[location.data_index];
let chunk = &mut data.chunks[location.chunk_index];
let component_index = chunk
.archetype
.query_component_index(&component_type::<T>())?;
Some(unsafe {
let ptr = chunk
.column_ptr(component_index)
.add(location.entity_index * std::mem::size_of::<T>());
&mut *(ptr as *mut T)
})
}
}