Skip to content

Commit

Permalink
SubGraphs, Views, Shadows, and more
Browse files Browse the repository at this point in the history
  • Loading branch information
cart committed Jul 24, 2021
1 parent 9588bb3 commit 3400fb4
Show file tree
Hide file tree
Showing 117 changed files with 4,714 additions and 1,817 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ default = [
"bevy_wgpu2",
"bevy_sprite2",
"bevy_render2",
"bevy_pbr2",
"bevy_winit",
"render",
"png",
Expand All @@ -54,6 +55,7 @@ bevy_winit = ["bevy_internal/bevy_winit"]
bevy_wgpu2 = ["bevy_internal/bevy_wgpu2"]
bevy_render2 = ["bevy_internal/bevy_render2"]
bevy_sprite2 = ["bevy_internal/bevy_sprite2"]
bevy_pbr2 = ["bevy_internal/bevy_pbr2"]

trace_chrome = ["bevy_internal/trace_chrome"]
trace = ["bevy_internal/trace"]
Expand Down Expand Up @@ -142,6 +144,10 @@ path = "examples/2d/texture_atlas.rs"
name = "3d_scene"
path = "examples/3d/3d_scene.rs"

[[example]]
name = "3d_scene_pipelined"
path = "examples/3d/3d_scene_pipelined.rs"

[[example]]
name = "load_gltf"
path = "examples/3d/load_gltf.rs"
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_ecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ impl ArchetypeId {
ArchetypeId(0)
}

#[inline]
pub const fn invalid() -> ArchetypeId {
ArchetypeId(usize::MAX)
}

#[inline]
pub const fn resource() -> ArchetypeId {
ArchetypeId(1)
Expand Down
94 changes: 74 additions & 20 deletions crates/bevy_ecs/src/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ pub struct Entity {
pub(crate) id: u32,
}

pub enum AllocAtWithoutReplacement {
Exists(EntityLocation),
DidNotExist,
ExistsWithWrongGeneration,
}

impl Entity {
/// Creates a new entity reference with a generation of 0.
pub fn new(id: u32) -> Entity {
Expand Down Expand Up @@ -242,11 +248,8 @@ impl Entities {
}

/// Allocate an entity ID directly.
///
/// Location should be written immediately.
pub fn alloc(&mut self) -> Entity {
self.verify_flushed();

self.len += 1;
if let Some(id) = self.pending.pop() {
let new_free_cursor = self.pending.len() as i64;
Expand Down Expand Up @@ -294,6 +297,40 @@ impl Entities {
loc
}

/// Allocate a specific entity ID, overwriting its generation.
///
/// Returns the location of the entity currently using the given ID, if any.
pub fn alloc_at_without_replacement(&mut self, entity: Entity) -> AllocAtWithoutReplacement {
self.verify_flushed();

let result = if entity.id as usize >= self.meta.len() {
self.pending.extend((self.meta.len() as u32)..entity.id);
let new_free_cursor = self.pending.len() as i64;
*self.free_cursor.get_mut() = new_free_cursor;
self.meta.resize(entity.id as usize + 1, EntityMeta::EMPTY);
self.len += 1;
AllocAtWithoutReplacement::DidNotExist
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.id) {
self.pending.swap_remove(index);
let new_free_cursor = self.pending.len() as i64;
*self.free_cursor.get_mut() = new_free_cursor;
self.len += 1;
AllocAtWithoutReplacement::DidNotExist
} else {
let current_meta = &mut self.meta[entity.id as usize];
if current_meta.location.archetype_id == ArchetypeId::invalid() {
AllocAtWithoutReplacement::DidNotExist
} else if current_meta.generation == entity.generation {
AllocAtWithoutReplacement::Exists(current_meta.location)
} else {
return AllocAtWithoutReplacement::ExistsWithWrongGeneration;
}
};

self.meta[entity.id as usize].generation = entity.generation;
result
}

/// Destroy an entity, allowing it to be reused.
///
/// Must not be called while reserved entities are awaiting `flush()`.
Expand Down Expand Up @@ -342,23 +379,13 @@ impl Entities {
self.len = 0;
}

/// Access the location storage of an entity.
///
/// Must not be called on pending entities.
pub fn get_mut(&mut self, entity: Entity) -> Option<&mut EntityLocation> {
let meta = &mut self.meta[entity.id as usize];
if meta.generation == entity.generation {
Some(&mut meta.location)
} else {
None
}
}

/// Returns `Ok(Location { archetype: 0, index: undefined })` for pending entities.
/// Returns `Ok(Location { archetype: Archetype::invalid(), index: undefined })` for pending entities.
pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
if (entity.id as usize) < self.meta.len() {
let meta = &self.meta[entity.id as usize];
if meta.generation != entity.generation {
if meta.generation != entity.generation
|| meta.location.archetype_id == ArchetypeId::invalid()
{
return None;
}
Some(meta.location)
Expand Down Expand Up @@ -401,7 +428,7 @@ impl Entities {

/// Allocates space for entities previously reserved with `reserve_entity` or
/// `reserve_entities`, then initializes each one using the supplied function.
pub fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) {
pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) {
let free_cursor = self.free_cursor.get_mut();
let current_free_cursor = *free_cursor;

Expand Down Expand Up @@ -439,6 +466,16 @@ impl Entities {
}
}

// Flushes all reserved entities to an "invalid" state. Attempting to retrieve them will return None
// unless they are later populated with a valid archetype.
pub fn flush_as_invalid(&mut self) {
unsafe {
self.flush(|_entity, location| {
location.archetype_id = ArchetypeId::invalid();
})
}
}

#[inline]
pub fn len(&self) -> u32 {
self.len
Expand All @@ -460,7 +497,7 @@ impl EntityMeta {
const EMPTY: EntityMeta = EntityMeta {
generation: 0,
location: EntityLocation {
archetype_id: ArchetypeId::empty(),
archetype_id: ArchetypeId::invalid(),
index: usize::max_value(), // dummy value, to be filled in
},
};
Expand Down Expand Up @@ -493,7 +530,24 @@ mod tests {
fn reserve_entity_len() {
let mut e = Entities::default();
e.reserve_entity();
e.flush(|_, _| {});
unsafe { e.flush(|_, _| {}) };
assert_eq!(e.len(), 1);
}

#[test]
fn get_reserved_and_invalid() {
let mut entities = Entities::default();
let e = entities.reserve_entity();
assert!(entities.contains(e));
assert!(entities.get(e).is_none());

unsafe {
entities.flush(|_entity, _location| {
// do nothing ... leaving entity location invalid
})
};

assert!(entities.contains(e));
assert!(entities.get(e).is_none());
}
}
127 changes: 127 additions & 0 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,7 @@ mod tests {
assert_eq!(dropped2.load(Ordering::Relaxed), 1);
}

#[test]
fn clear_entities() {
let mut world = World::default();
world
Expand Down Expand Up @@ -1266,4 +1267,130 @@ mod tests {
"world should still contain resources"
);
}

#[test]
fn reserve_entities_across_worlds() {
let mut world_a = World::default();
let mut world_b = World::default();

let e1 = world_a.spawn().insert(1u32).id();
let e2 = world_a.spawn().insert(2u32).id();
let e3 = world_a.entities().reserve_entity();
world_a.flush();

let world_a_max_entities = world_a.entities().meta.len();
world_b
.entities
.reserve_entities(world_a_max_entities as u32);
world_b.entities.flush_as_invalid();

let e4 = world_b.spawn().insert(4u32).id();
assert_eq!(
e4,
Entity {
generation: 0,
id: 3,
},
"new entity is created immediately after world_a's max entity"
);
assert!(world_b.get::<u32>(e1).is_none());
assert!(world_b.get_entity(e1).is_none());

assert!(world_b.get::<u32>(e2).is_none());
assert!(world_b.get_entity(e2).is_none());

assert!(world_b.get::<u32>(e3).is_none());
assert!(world_b.get_entity(e3).is_none());

world_b.get_or_spawn(e1).unwrap().insert(1.0f32);
assert_eq!(
world_b.get::<f32>(e1),
Some(&1.0f32),
"spawning into 'world_a' entities works"
);

world_b.get_or_spawn(e4).unwrap().insert(4.0f32);
assert_eq!(
world_b.get::<f32>(e4),
Some(&4.0f32),
"spawning into existing `world_b` entities works"
);
assert_eq!(
world_b.get::<u32>(e4),
Some(&4u32),
"spawning into existing `world_b` entities works"
);

let e4_mismatched_generation = Entity {
generation: 1,
id: 3,
};
assert!(
world_b.get_or_spawn(e4_mismatched_generation).is_none(),
"attempting to spawn on top of an entity with a mismatched entity generation fails"
);
assert_eq!(
world_b.get::<f32>(e4),
Some(&4.0f32),
"failed mismatched spawn doesn't change existing entity"
);
assert_eq!(
world_b.get::<u32>(e4),
Some(&4u32),
"failed mismatched spawn doesn't change existing entity"
);

let high_non_existent_entity = Entity {
generation: 0,
id: 6,
};
world_b
.get_or_spawn(high_non_existent_entity)
.unwrap()
.insert(10.0f32);
assert_eq!(
world_b.get::<f32>(high_non_existent_entity),
Some(&10.0f32),
"inserting into newly allocated high / non-continous entity id works"
);

let high_non_existent_but_reserved_entity = Entity {
generation: 0,
id: 5,
};
assert!(
world_b.get_entity(high_non_existent_but_reserved_entity).is_none(),
"entities between high-newly allocated entity and continuous block of existing entities don't exist"
);

let reserved_entities = vec![
world_b.entities().reserve_entity(),
world_b.entities().reserve_entity(),
world_b.entities().reserve_entity(),
world_b.entities().reserve_entity(),
];

assert_eq!(
reserved_entities,
vec![
Entity {
generation: 0,
id: 5
},
Entity {
generation: 0,
id: 4
},
Entity {
generation: 0,
id: 7,
},
Entity {
generation: 0,
id: 8,
},
],
"space between original entities and high entities is used for new entity ids"
);
}
}
Loading

0 comments on commit 3400fb4

Please sign in to comment.