-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Improved Entity Lifecycle: remove flushing, support manual spawning and despawning #19451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 79 commits
6a7c2e9
65a7ea9
4c6f613
4eab25c
8556b19
f4e06ad
3df6ab0
7b044c0
5c58266
01d6785
c100f9e
ea9a3be
0c1c9c3
5a69ebf
019c154
58ee663
85b0d03
4844dda
7e39f9d
9cce28f
b415dc8
38a9710
8956adc
0c194b7
180b349
20a6ee3
068a2a9
763877f
9baf15a
63effde
7967807
866476e
531658c
d899b38
eabc340
9df7a76
6a596cb
5ca8e38
91f439c
9d86e69
d86d241
d0ed587
80479d0
185a6de
4f956bd
6bbfee8
b543a4a
10f11ba
e78deec
5422813
e1b5386
271c189
9de86df
0b3da34
fb897d2
1b6f327
5085d4f
20386ff
4c0d752
ab0856b
71675a3
fdf1b55
f9b26d6
e129d77
d42d9ea
bac7bd2
e9df300
3fcbb24
d0ca8b1
4bb74b2
deb6b4d
5a058c0
e73f7bf
3062966
01aa792
caf0581
9c56341
9981716
762c65b
b743a44
2add4ca
97047a9
fedc90a
ecf617f
6d12925
45780c2
57816f7
5c69793
d8dac49
26eb50c
a16cafd
ac34213
be9f104
f525517
2abdf9e
4d074e4
cc71f17
46e370b
3781e72
3d766ae
9ecbb2d
dd28156
46b6b83
ffb495d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,20 @@ | ||
| use alloc::{boxed::Box, collections::VecDeque, vec::Vec}; | ||
| use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; | ||
| use bevy_ptr::{Ptr, PtrMut}; | ||
| use bevy_utils::prelude::DebugName; | ||
| use bumpalo::Bump; | ||
| use core::{any::TypeId, cell::LazyCell, ops::Range}; | ||
| use derive_more::derive::From; | ||
|
|
||
| use crate::{ | ||
| archetype::Archetype, | ||
| bundle::{Bundle, BundleRemover, InsertMode}, | ||
| change_detection::MaybeLocation, | ||
| component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo}, | ||
| entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper}, | ||
| entity::{hash_map::EntityHashMap, EntitiesAllocator, Entity, EntityMapper}, | ||
| query::DebugCheckedUnwrap, | ||
| relationship::RelationshipHookMode, | ||
| world::World, | ||
| world::{unsafe_world_cell::UnsafeEntityCell, World}, | ||
| }; | ||
| use alloc::{boxed::Box, collections::VecDeque, vec::Vec}; | ||
| use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; | ||
| use bevy_ptr::{Ptr, PtrMut}; | ||
| use bevy_utils::prelude::DebugName; | ||
| use bumpalo::Bump; | ||
| use core::{any::TypeId, cell::LazyCell, ops::Range}; | ||
| use derive_more::From; | ||
|
|
||
| /// Provides read access to the source component (the component being cloned) in a [`ComponentCloneFn`]. | ||
| pub struct SourceComponent<'a> { | ||
|
|
@@ -80,7 +79,7 @@ pub struct ComponentCloneCtx<'a, 'b> { | |
| target_component_moved: bool, | ||
| bundle_scratch: &'a mut BundleScratch<'b>, | ||
| bundle_scratch_allocator: &'b Bump, | ||
| entities: &'a Entities, | ||
| allocator: &'a EntitiesAllocator, | ||
| source: Entity, | ||
| target: Entity, | ||
| component_info: &'a ComponentInfo, | ||
|
|
@@ -106,7 +105,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { | |
| target: Entity, | ||
| bundle_scratch_allocator: &'b Bump, | ||
| bundle_scratch: &'a mut BundleScratch<'b>, | ||
| entities: &'a Entities, | ||
| allocator: &'a EntitiesAllocator, | ||
| component_info: &'a ComponentInfo, | ||
| entity_cloner: &'a mut EntityClonerState, | ||
| mapper: &'a mut dyn EntityMapper, | ||
|
|
@@ -121,7 +120,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { | |
| target_component_written: false, | ||
| target_component_moved: false, | ||
| bundle_scratch_allocator, | ||
| entities, | ||
| allocator, | ||
| mapper, | ||
| component_info, | ||
| state: entity_cloner, | ||
|
|
@@ -279,7 +278,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { | |
|
|
||
| /// Queues the `entity` to be cloned by the current [`EntityCloner`] | ||
| pub fn queue_entity_clone(&mut self, entity: Entity) { | ||
| let target = self.entities.reserve_entity(); | ||
| let target = self.allocator.alloc(); | ||
| self.mapper.set_mapped(entity, target); | ||
| self.state.clone_queue.push_back(entity); | ||
| } | ||
|
|
@@ -565,14 +564,22 @@ impl EntityCloner { | |
| relationship_hook_insert_mode: RelationshipHookMode, | ||
| ) -> Entity { | ||
| let target = mapper.get_mapped(source); | ||
| // The target may need to be constructed if it hasn't been already. | ||
| // If this fails, it either didn't need to be constructed (ok) or doesn't exist (caught better later). | ||
| let _ = world.construct_empty(target); | ||
|
|
||
| // PERF: reusing allocated space across clones would be more efficient. Consider an allocation model similar to `Commands`. | ||
| let bundle_scratch_allocator = Bump::new(); | ||
| let mut bundle_scratch: BundleScratch; | ||
| let mut moved_components: Vec<ComponentId> = Vec::new(); | ||
| let mut deferred_cloned_component_ids: Vec<ComponentId> = Vec::new(); | ||
| { | ||
| let world = world.as_unsafe_world_cell(); | ||
| let source_entity = world.get_entity(source).expect("Source entity must exist"); | ||
| let (source_archetype, source_entity) = world | ||
| .get_entity(source) | ||
| .ok() | ||
| .and_then(|source| source.archetype().map(|archetype| (archetype, source))) | ||
| .expect("Source entity must exist constructed"); | ||
ElliottjPierce marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| #[cfg(feature = "bevy_reflect")] | ||
| // SAFETY: we have unique access to `world`, nothing else accesses the registry at this moment, and we clone | ||
|
|
@@ -585,14 +592,14 @@ impl EntityCloner { | |
| #[cfg(not(feature = "bevy_reflect"))] | ||
| let app_registry = Option::<()>::None; | ||
|
|
||
| let source_archetype = source_entity.archetype(); | ||
| bundle_scratch = BundleScratch::with_capacity(source_archetype.component_count()); | ||
|
|
||
| let target_archetype = LazyCell::new(|| { | ||
| world | ||
| .get_entity(target) | ||
| .expect("Target entity must exist") | ||
| .archetype() | ||
| .ok() | ||
| .and_then(UnsafeEntityCell::archetype) | ||
| .expect("Target entity must exist constructed") | ||
|
||
| }); | ||
|
|
||
| if state.move_components { | ||
|
|
@@ -641,7 +648,7 @@ impl EntityCloner { | |
| target, | ||
| &bundle_scratch_allocator, | ||
| &mut bundle_scratch, | ||
| world.entities(), | ||
| world.entities_allocator(), | ||
| info, | ||
| state, | ||
| mapper, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do indeed think this should be
indexand notrow.Entitiesis not aTablefunctionally. It is an array. Yes every array in a certain light could be considered to be a single column table. But that is not how we approach that in the context of Bevy or Rust. Even in the context of Bevy ECS specifically I find it mismatched, asTableis a specific thing, and Entities is not that thing. I'd prefer to fix this everywhere in this PR, rather than spread therowterminology further.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I have lots to say here. If you want to unilaterally declare "no rows", I understand and will do that, but before I do that, I just want to explain myself a little bit here, because I much prefer
rowtoindex.You mentioned that using row names makes it hard to untie it from
Tablestorage. That's fair. I don't have a good answer for that.But, in the very common "ecs as a spreadsheet" example, this makes a lot of sense. For people learning the ecs way, I think this name is much more approachable than index. Index naming ties the high level concept to the implementation of the
Entitiescollection.And that implementation is not unlikely to change. For example, with entity paging, this "index" becomes a key into a 2-layer array map. You mentioned elsewhere, I assume for the sake of argument, that
Entitymight become aUUID, and that should be possible without needing to change names. In that case, theEntityRowwould be the key in a hashmap, not an index in an array.I guess what I'm saying is that using the index name ties the name to its implementation details, an index in an array, rather than the high-level concept, a row in a spreadsheet. And that seems contrary to your motivations behind other naming suggestions, most of which I agree with.
Again, I'll happily rename everything to index. I just don't want that to come back to bite us latter if we do entity paging, for example, and people start wondering "Why is this called index? It's not an index into anything.".
What do you think?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remain unconvinced. The "ECS is just tables" statement is perhaps a helpful lie, but it is not the truth of the matter. Bevy ECS is partially tables and partially other things, and that will continue to be true. Given that it is partially real tables, pretending everything is tables makes the internals unnecessarily confusing, and it makes communicating how the system actually works to developers more challenging. That being said, even if we were to choose to tell that lie, I'll assert that this naming scheme is doing it wrong.
First: what is a "row" in a table? It is a specific entry in the table. A "row identifier" is a unique name for the row that can be stored (ex: the "first" row might be row A, row 0, or row 1, depending on the naming scheme). In our case, our naming scheme is "array indices". If we are storing the "row identifier", we are storing the array index of the row. If I have a field or type called
row, I expect it to be the "row identifier".Calling it
Entity::row, makes no sense because it is not the row identifier. It is a unique index that identifies the entity, not the row it is stored in. The row an entity is stored in can be constantly changing, while the entity's index remains unchanged. That index points to a location in an array that stores arbitrary information about that entity, which includes the archetype it is stored in, the table, etc. There is a world where that entity metadata stores multiple table rows, if we decide to support grouping some sets of components into separate tables (for performance reasons ... Sander and I have discussed this at various points and Flecs might actually already support it).Even in the "ECS is just tables" / "ECS is just a database" world,
Entity::indexwould be the "primary key" stored as a column in a row, not the "row":Table
Sparse Set
Where that primary key then has an acceleration structure to find it's locations (filling exactly the same role that
Entitiesfills)I agree that
indexis an implementation detail. If someone is relying onEntity::index, they are relying on a Bevy ECS implementation detail. If we change that to include paging, or to use a UUID, the consumer ofindexin many cases will need to contend with that. A contract (and perhaps naming) change is warranted.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly to
constructvsspawn_at, I still very much disagree here but am also ok with you're decision.For one, I think you're conflating tables + sparse sets as implementation and ecs as a database with "think of an ecs as a spreadsheet." The row in the spreadsheet has nothing to do with how the spreadsheet is represented in memory or how it is implemented. The "row in a spreadsheet" is purely allegorical to help users. I think it is the most common mental framework for thinking of an ecs (again, not in implementation but in concept). At least that's how I was introduced to it and still think about ecs philosophy. I think that's very common, and is the typical mental framework that users are coming from. The rest of the ecs is just figuring out how to make reading the spreadsheet and finding the components of a row in the spreadsheet faster than it has any right to be.
I'm also still concerned about tying the names to implementation details instead of higher level concepts, especially when you've argued for the exact opposite for other names. (I don't mean to attack you here. But I am confused.) Those names were more user-focused. Maybe that's the difference? IDK. Maybe I'm not understanding, but what's confusing me a little is that I'm not sure what criteria you're using to determine implementation-centered names vs concept-centered names. And that makes it much harder (for me) to follow your logic.
Anyway, I don't think I'm going to convince you, and that's ok.
If I understand correctly, you want this name in particular to reflect how
Entitiesis implemented. And that means renaming to index for now and then later, with entity paging, maybe renaming again to "key" or "entry" or something.Assuming that's correct, I'll finish this up either latter tonight or tomorrow. But do let me know otherwise. And I'd also still like some help (just for future reference and my own learning) understanding the criteria you're using to determine implementation-centered names vs concept-centered names. I don't have a good mental picture for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The distinction is that
EntityandEntity::indexare at different levels of abstraction, and abstractions have to end somewhere. From my perspective,Entityshould be an opaque unique primary key, which people can use as such without worrying about whats going on inside. The internals are an implementation detail, and therefore benefit from being functionally descriptive. People can reach in to look at what is inside, but they are just "internals", and generally should not be depended in user code.This is of course an art and not a science. We could decide to abstract over the internals of
Entity, with the goal to make them stable. But I don't really see the point of doing that. The cost of functional clarity and additional layers of abstraction isn't worth the stability, at that level, as we already have a higher level abstraction providing that stability.Cool lets move forward with the "index' terminology, and cut the "ecs as spreadsheets" section for now. I'm not fully against using the "ECS is like a spreadsheet" angle as a teaching tool, but I still strongly object to the Entity == Row angle, and I really don't want to block this PR on that conversation.