diff --git a/flecs_ecs/src/addons/doc.rs b/flecs_ecs/src/addons/doc.rs index 4922d652..facc6b0b 100644 --- a/flecs_ecs/src/addons/doc.rs +++ b/flecs_ecs/src/addons/doc.rs @@ -36,10 +36,10 @@ pub trait Doc<'a>: WorldProvider<'a> + Into + Clone { /// /// # See also /// - /// * [`World::get_doc_name()`] + /// * [`World::doc_name()`] /// * C++ API: `doc::get_name()` - fn get_doc_name(&self) -> Option { - self.world().get_doc_name_id(self.clone()) + fn doc_name(&self) -> Option { + self.world().doc_name_id(self.clone()) } /// Get brief description for an entity. @@ -50,10 +50,10 @@ pub trait Doc<'a>: WorldProvider<'a> + Into + Clone { /// /// # See also /// - /// * [`World::get_doc_brief()`] + /// * [`World::doc_brief()`] /// * C++ API: `doc::get_brief()` - fn get_doc_brief(&self) -> Option { - self.world().get_doc_brief_id(self.clone()) + fn doc_brief(&self) -> Option { + self.world().doc_brief_id(self.clone()) } /// Get detailed description for an entity. @@ -64,10 +64,10 @@ pub trait Doc<'a>: WorldProvider<'a> + Into + Clone { /// /// # See also /// - /// * [`World::get_doc_detail()`] + /// * [`World::doc_detail()`] /// * C++ API: `doc::get_detail()` - fn get_doc_detail(&self) -> Option { - self.world().get_doc_detail_id(self.clone()) + fn doc_detail(&self) -> Option { + self.world().doc_detail_id(self.clone()) } /// Get link to external documentation for an entity. @@ -78,10 +78,10 @@ pub trait Doc<'a>: WorldProvider<'a> + Into + Clone { /// /// # See also /// - /// * [`World::get_doc_link()`] + /// * [`World::doc_link()`] /// * C++ API: `doc::get_link()` - fn get_doc_link(&self) -> Option { - self.world().get_doc_link_id(self.clone()) + fn doc_link(&self) -> Option { + self.world().doc_link_id(self.clone()) } /// Get color for an entity. @@ -92,10 +92,28 @@ pub trait Doc<'a>: WorldProvider<'a> + Into + Clone { /// /// # See also /// - /// * [`World::get_doc_color()`] + /// * [`World::doc_color()`] /// * C++ API: `doc::get_color()` - fn get_doc_color(&self) -> Option { - self.world().get_doc_color_id(self.clone()) + fn doc_color(&self) -> Option { + self.world().doc_color_id(self.clone()) + } + + /// Get UUID for entity + /// + /// # Returns + /// + /// The UUID of the entity. + /// + /// # See also + /// + /// * [`World::doc_uuid_id()`] + /// * [`World::doc_uuid()`] + /// * [`Doc::set_doc_uuid()`] + /// * [`World::set_doc_uuid_id()`] + /// * [`World::set_doc_uuid()`] + /// * C++ API: `doc::get_uuid()` + fn doc_uuid(&self) -> Option { + self.world().doc_uuid_id(self.clone()) } //MARK: _setters @@ -185,6 +203,26 @@ pub trait Doc<'a>: WorldProvider<'a> + Into + Clone { self.world().set_doc_color_id(self.clone(), color); self } + + /// Set doc UUID. + /// This adds `(flecs.doc.Description, flecs.doc.Uuid)` to the entity. + /// + /// # Arguments + /// + /// * `uuid` - The UUID to add. + /// + /// # See also + /// * [`World::set_doc_uuid_id()`] + /// * [`World::set_doc_uuid()`] + /// * [`World::doc_uuid()`] + /// * [`World::doc_uuid_id()`] + /// * [`Doc::doc_uuid()`] + /// * C++ API: `entity_builder::set_doc_uuid` + #[doc(alias = "entity_builder::set_doc_uuid")] + fn set_doc_uuid(&self, uuid: &str) -> &Self { + self.world().set_doc_uuid_id(self.clone(), uuid); + self + } } impl<'a, T> Doc<'a> for T where T: Into + WorldProvider<'a> + Clone {} @@ -215,13 +253,13 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_name()`] - /// * [`World::get_doc_name_id()`] + /// * [`Doc::doc_name()`] + /// * [`World::doc_name_id()`] /// * C++ API: `doc::get_name()` #[doc(alias = "doc::get_name")] #[inline(always)] - pub fn get_doc_name(&self) -> Option { - self.get_doc_name_id(T::get_id(self)) + pub fn doc_name(&self) -> Option { + self.doc_name_id(T::get_id(self)) } /// Get human readable name for an entity. @@ -236,12 +274,12 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_name()`] - /// * [`World::get_doc_name()`] + /// * [`Doc::doc_name()`] + /// * [`World::doc_name()`] /// * C++ API: `world::get_doc_name()` #[doc(alias = "doc::get_name")] #[inline(always)] - pub fn get_doc_name_id(&self, entity: impl Into) -> Option { + pub fn doc_name_id(&self, entity: impl Into) -> Option { let cstr = NonNull::new( unsafe { sys::ecs_doc_get_name(self.world_ptr(), *entity.into()) } as *mut _, ) @@ -261,13 +299,13 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_brief()`] - /// * [`World::get_doc_brief_id()`] + /// * [`Doc::doc_brief()`] + /// * [`World::doc_brief_id()`] /// * C++ API: `doc::get_brief()` #[doc(alias = "doc::get_brief")] #[inline(always)] - pub fn get_doc_brief(&self) -> Option { - self.get_doc_brief_id(T::get_id(self)) + pub fn doc_brief(&self) -> Option { + self.doc_brief_id(T::get_id(self)) } /// Get brief description for an entity. @@ -282,12 +320,12 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_brief()`] - /// * [`World::get_doc_brief()`] + /// * [`Doc::doc_brief()`] + /// * [`World::doc_brief()`] /// * C++ API: `doc::get_brief` #[doc(alias = "doc::get_brief")] #[inline(always)] - pub fn get_doc_brief_id(&self, entity: impl Into) -> Option { + pub fn doc_brief_id(&self, entity: impl Into) -> Option { let cstr = NonNull::new( unsafe { sys::ecs_doc_get_brief(self.world_ptr(), *entity.into()) } as *mut _, ) @@ -306,13 +344,13 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_detail()`] - /// * [`World::get_doc_detail_id()`] + /// * [`Doc::doc_detail()`] + /// * [`World::doc_detail_id()`] /// * C++ API: `doc::get_detail()` #[doc(alias = "doc::get_detail")] #[inline(always)] - pub fn get_doc_detail(&self) -> Option { - self.get_doc_detail_id(T::get_id(self)) + pub fn doc_detail(&self) -> Option { + self.doc_detail_id(T::get_id(self)) } /// Get detailed description for an entity. @@ -327,12 +365,12 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_detail()`] - /// * [`World::get_doc_detail()`] + /// * [`Doc::doc_detail()`] + /// * [`World::doc_detail()`] /// * C++ API: `doc::get_detail` #[doc(alias = "doc::get_detail")] #[inline(always)] - pub fn get_doc_detail_id(&self, entity: impl Into) -> Option { + pub fn doc_detail_id(&self, entity: impl Into) -> Option { let cstr = NonNull::new( unsafe { sys::ecs_doc_get_detail(self.world_ptr(), *entity.into()) } as *mut _, ) @@ -352,13 +390,13 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_link()`] - /// * [`World::get_doc_link_id()`] + /// * [`Doc::doc_link()`] + /// * [`World::doc_link_id()`] /// * C++ API: `doc::get_link()` #[doc(alias = "doc::get_link")] #[inline(always)] - pub fn get_doc_link(&self) -> Option { - self.get_doc_link_id(T::get_id(self)) + pub fn doc_link(&self) -> Option { + self.doc_link_id(T::get_id(self)) } /// Get link to external documentation for an entity. @@ -372,12 +410,12 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_link()`] - /// * [`World::get_doc_link()`] + /// * [`Doc::doc_link()`] + /// * [`World::doc_link()`] /// * C++ API: `doc::get_link` #[doc(alias = "doc::get_link")] #[inline(always)] - pub fn get_doc_link_id(&self, entity: impl Into) -> Option { + pub fn doc_link_id(&self, entity: impl Into) -> Option { let cstr = NonNull::new( unsafe { sys::ecs_doc_get_link(self.world_ptr(), *entity.into()) } as *mut _, ) @@ -396,13 +434,13 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_color()`] - /// * [`World::get_doc_color_id()`] + /// * [`Doc::doc_color()`] + /// * [`World::doc_color_id()`] /// * C++ API: `doc::get_color()` #[doc(alias = "doc::get_color")] #[inline(always)] - pub fn get_doc_color(&self) -> Option { - self.get_doc_color_id(T::get_id(self)) + pub fn doc_color(&self) -> Option { + self.doc_color_id(T::get_id(self)) } /// Get color for an entity. @@ -416,12 +454,12 @@ impl World { /// /// # See also /// - /// * [`Doc::get_doc_color()`] - /// * [`World::get_doc_color()`] + /// * [`Doc::doc_color()`] + /// * [`World::doc_color()`] /// * C++ API: `doc::get_color` #[doc(alias = "doc::get_color")] #[inline(always)] - pub fn get_doc_color_id(&self, entity: impl Into) -> Option { + pub fn doc_color_id(&self, entity: impl Into) -> Option { let cstr = NonNull::new( unsafe { sys::ecs_doc_get_color(self.world_ptr(), *entity.into()) } as *mut _, ) @@ -429,6 +467,42 @@ impl World { cstr.and_then(|s| s.to_str().ok().map(|s| s.to_string())) } + /// Get UUID for entity + /// + /// # See Also + /// + /// * [`World::doc_uuid()`] + /// * [`Doc::doc_uuid()`] + /// * [`World::set_doc_uuid_id()`] + /// * [`Doc::set_doc_uuid()`] + /// * C++ API: `doc::get_uuid` + #[doc(alias = "doc::get_uuid")] + pub fn doc_uuid_id(&self, entity: impl Into) -> Option { + let cstr = NonNull::new( + unsafe { sys::ecs_doc_get_uuid(self.world_ptr(), *entity.into()) } as *mut _, + ) + .map(|s| unsafe { CStr::from_ptr(s.as_ptr()) }); + cstr.and_then(|s| s.to_str().ok().map(|s| s.to_string())) + } + + /// Get UUID for component + /// + /// # Type Parameters + /// + /// * `T` - The component. + /// + /// # See Also + /// + /// * [`World::doc_uuid_id()`] + /// * [`Doc::doc_uuid()`] + /// * [`World::set_doc_uuid_id()`] + /// * [`Doc::set_doc_uuid()`] + /// * C++ API: `doc::get_uuid` + #[doc(alias = "doc::get_uuid")] + pub fn doc_uuid(&self) -> Option { + self.doc_uuid_id(T::get_id(self)) + } + //MARK: _World::setters /// Add human-readable name to entity. @@ -601,7 +675,7 @@ impl World { unsafe { sys::ecs_doc_set_link(self.ptr_mut(), *entity.into(), link.as_ptr() as *const _) }; } - /// Add color to entity. + /// Add color to component. /// /// UIs can use color as hint to improve visualizing entities. /// @@ -646,6 +720,54 @@ impl World { sys::ecs_doc_set_color(self.ptr_mut(), *entity.into(), color.as_ptr() as *const _); }; } + + /// Add UUID to entity. + /// This adds `(flecs.doc.Description, flecs.doc.Uuid)` to the entity. + /// + /// # Arguments + /// + /// * `entity` - The entity to which to add the UUID. + /// * `uuid` - The UUID to add. + /// + /// # See also + /// + /// * [`Doc::set_doc_uuid()`] + /// * [`World::set_doc_uuid()`] + /// * [`World::doc_uuid()`] + /// * [`World::doc_uuid_id()`] + /// * [`Doc::doc_uuid()`] + /// * C++ API: `entity_builder::set_doc_uuid` + #[doc(alias = "entity_builder::set_doc_uuid")] + pub fn set_doc_uuid_id(&self, entity: impl Into, uuid: &str) { + let uuid = compact_str::format_compact!("{}\0", uuid); + unsafe { + sys::ecs_doc_set_uuid(self.ptr_mut(), *entity.into(), uuid.as_ptr() as *const _); + }; + } + + /// Add UUID to component + /// This adds `(flecs.doc.Description, flecs.doc.Uuid)` to the component + /// + /// # Type Parameters + /// + /// * `T` - The component. + /// + /// # Arguments + /// + /// * `uuid` - The UUID to add. + /// + /// # See also + /// + /// * [`World::set_doc_uuid_id()`] + /// * [`Doc::set_doc_uuid()`] + /// * [`World::doc_uuid()`] + /// * [`World::doc_uuid_id()`] + /// * [`Doc::doc_uuid()`] + /// * C++ API: `entity_builder::set_doc_uuid` + #[doc(alias = "entity_builder::set_doc_uuid")] + pub fn set_doc_uuid(&self, uuid: &str) { + self.set_doc_uuid_id(T::get_id(self), uuid); + } } #[test] diff --git a/flecs_ecs/src/addons/metrics/mod.rs b/flecs_ecs/src/addons/metrics/mod.rs index 92b612e6..4bb7a282 100644 --- a/flecs_ecs/src/addons/metrics/mod.rs +++ b/flecs_ecs/src/addons/metrics/mod.rs @@ -17,7 +17,7 @@ impl<'a> UntypedComponent<'a> { /// When the member name is `"value"`, the operation will use the name of the component instead. /// /// If the `brief` parameter is provided, it is set on the metric as if [`set_doc_brief`][crate::addons::doc::Doc::set_doc_brief] was called. - /// The brief description can be retrieved using [`get_doc_brief`][crate::addons::doc::Doc::get_doc_brief]. + /// The brief description can be retrieved using [`get_doc_brief`][crate::addons::doc::Doc::doc_brief]. /// /// # Type Parameters /// @@ -31,7 +31,7 @@ impl<'a> UntypedComponent<'a> { /// /// # See also /// - /// * [`get_doc_brief`][crate::addons::doc::Doc::get_doc_brief] + /// * [`get_doc_brief`][crate::addons::doc::Doc::doc_brief] /// * [`set_doc_brief`][crate::addons::doc::Doc::set_doc_brief] pub fn metric( &self, diff --git a/flecs_ecs/src/addons/module.rs b/flecs_ecs/src/addons/module.rs index 14338822..34f46249 100644 --- a/flecs_ecs/src/addons/module.rs +++ b/flecs_ecs/src/addons/module.rs @@ -145,7 +145,6 @@ impl World { /// * [`World::import()`] /// * C++ API: `world::module` pub fn module(&self, name: &str) -> EntityView { - self.defer_begin(); let comp = self.component::(); let id = comp.id(); @@ -168,7 +167,7 @@ impl World { let mut cur = prev_parent; let mut next; - loop { + while cur.id != 0 { next = cur.parent().unwrap_or(EntityView::new_null(self)); let mut it = unsafe { @@ -188,7 +187,6 @@ impl World { } } } - self.defer_end(); //self.set_scope_id(id); EntityView::new_from(self, *id) diff --git a/flecs_ecs/src/core/c_types.rs b/flecs_ecs/src/core/c_types.rs index 4b688fc9..0100ecb6 100644 --- a/flecs_ecs/src/core/c_types.rs +++ b/flecs_ecs/src/core/c_types.rs @@ -477,9 +477,10 @@ pub(crate) const ECS_DOC_BRIEF: u64 = FLECS_HI_COMPONENT_ID + 114; pub(crate) const ECS_DOC_DETAIL: u64 = FLECS_HI_COMPONENT_ID + 115; pub(crate) const ECS_DOC_LINK: u64 = FLECS_HI_COMPONENT_ID + 116; pub(crate) const ECS_DOC_COLOR: u64 = FLECS_HI_COMPONENT_ID + 117; +pub(crate) const ECS_DOC_UUID: u64 = FLECS_HI_COMPONENT_ID + 118; // REST module components -pub(crate) const ECS_REST: u64 = FLECS_HI_COMPONENT_ID + 118; +pub(crate) const ECS_REST: u64 = FLECS_HI_COMPONENT_ID + 119; macro_rules! impl_component_traits_binding_type_w_id { ($name:ident, $id:ident) => { diff --git a/flecs_ecs/src/core/entity_view/entity_view_mut.rs b/flecs_ecs/src/core/entity_view/entity_view_mut.rs index c08c4725..2ba3552e 100644 --- a/flecs_ecs/src/core/entity_view/entity_view_mut.rs +++ b/flecs_ecs/src/core/entity_view/entity_view_mut.rs @@ -1006,6 +1006,7 @@ impl<'a> EntityView<'a> { let first_id = *first.into(); let second_id = Second::id(self.world); let pair_id = ecs_pair(first_id, second_id); + // NOTE: we could this safety check optional let data_id = unsafe { sys::ecs_get_typeid(world, pair_id) }; if data_id != second_id { @@ -1464,7 +1465,7 @@ impl<'a> EntityView<'a> { pub fn modified(&self) { const { assert!( - std::mem::size_of::() != 0, + std::mem::size_of::() != 0, "cannot modify zero-sized-type / tag components" ); }; @@ -1513,12 +1514,12 @@ impl<'a> EntityView<'a> { /// /// * C++ API: `entity::get_ref` #[doc(alias = "entity::get_ref")] - pub fn get_ref(&self) -> CachedRef<'a, T::UnderlyingType> + pub fn get_ref(&self) -> CachedRef<'a, T::CastType> where - T: ComponentId + DataComponent, - T::UnderlyingType: DataComponent, + T: ComponentOrPairId, + T::CastType: DataComponent, { - CachedRef::::new(self.world, *self.id, T::id(self.world)) + CachedRef::::new(self.world, *self.id, T::get_id(self.world)) } /// Get a reference to the first component of pair @@ -1546,11 +1547,20 @@ impl<'a> EntityView<'a> { self, second: impl Into, ) -> CachedRef<'a, First> { - CachedRef::::new( - self.world, - *self.id, - ecs_pair(First::id(self.world), *second.into()), - ) + let first = First::id(self.world); + let second = *second.into(); + let pair = ecs_pair(first, second); + ecs_assert!( + !(unsafe { sys::ecs_get_type_info(self.world.world_ptr(), pair,) }.is_null()), + FlecsErrorCode::InvalidParameter, + "pair is not a component" + ); + ecs_assert!( + unsafe { *sys::ecs_get_type_info(self.world.world_ptr(), pair,) }.component == first, + FlecsErrorCode::InvalidParameter, + "type of pair is not First" + ); + CachedRef::::new(self.world, *self.id, pair) } /// Get a reference to the second component of pair @@ -1578,11 +1588,21 @@ impl<'a> EntityView<'a> { &self, first: impl Into, ) -> CachedRef { - CachedRef::::new( - self.world, - *self.id, - ecs_pair(*first.into(), Second::id(self.world)), - ) + let first = *first.into(); + let second = Second::id(self.world); + let pair = ecs_pair(first, second); + ecs_assert!( + !(unsafe { sys::ecs_get_type_info(self.world.world_ptr(), pair,) }.is_null()), + FlecsErrorCode::InvalidParameter, + "pair is not a component" + ); + ecs_assert!( + unsafe { *sys::ecs_get_type_info(self.world.world_ptr(), pair,) }.component == second, + FlecsErrorCode::InvalidParameter, + "type of pair is not Second" + ); + + CachedRef::::new(self.world, *self.id, pair) } /// Clear an entity. diff --git a/flecs_ecs/src/core/flecs.rs b/flecs_ecs/src/core/flecs.rs index f5f55661..5558f8f9 100644 --- a/flecs_ecs/src/core/flecs.rs +++ b/flecs_ecs/src/core/flecs.rs @@ -508,6 +508,7 @@ pub mod doc { create_pre_registered_component!(Detail, ECS_DOC_DETAIL); create_pre_registered_component!(Link, ECS_DOC_LINK); create_pre_registered_component!(Color, ECS_DOC_COLOR); + create_pre_registered_component!(UUID, ECS_DOC_UUID); } #[cfg(feature = "flecs_rest")] diff --git a/flecs_ecs/src/core/observer_builder.rs b/flecs_ecs/src/core/observer_builder.rs index fccf1e9f..faa2b458 100644 --- a/flecs_ecs/src/core/observer_builder.rs +++ b/flecs_ecs/src/core/observer_builder.rs @@ -7,7 +7,7 @@ use crate::core::private::internal_SystemAPI; use crate::core::*; use crate::sys; -/// `ObserverBuilder` is used to configure and build [`Observer`]s. +/// [`ObserverBuilder`] is used to configure and build [`Observer`]s. /// /// Observers are systems that react to events. /// Observers let applications register callbacks for ECS events. @@ -139,6 +139,21 @@ impl<'a, P, T: QueryTuple> ObserverBuilder<'a, P, T> { } impl<'a, P, T: QueryTuple> ObserverBuilder<'a, P, T> { + /// set observer flags, which are the same as Query flags + /// + /// # Arguments + /// + /// * `flags` - the flags to set + /// + /// # See also + /// + /// * C++ API: `query_builder_i::filter_flags` + #[doc(alias = "query_builder_i::filter_flags")] + fn observer_flags(&mut self, flags: QueryFlags) -> &mut Self { + self.desc.flags_ |= flags.bits(); + self + } + /// Specify the event(s) for when the observer should run. /// /// # Arguments diff --git a/flecs_ecs/src/core/table/iter.rs b/flecs_ecs/src/core/table/iter.rs index bbbf0c1a..633366f4 100644 --- a/flecs_ecs/src/core/table/iter.rs +++ b/flecs_ecs/src/core/table/iter.rs @@ -920,6 +920,83 @@ where } } } + + /// Iterate targets for pair field. + /// + /// # Arguments + /// + /// * index: the field index + /// * func: callback invoked for each target with the signature fn(EntityView entity) + /// + /// # Example + /// + /// ``` + /// + /// use flecs_ecs::prelude::*; + /// + /// let world = World::new(); + /// + /// let likes = world.entity(); + /// let pizza = world.entity(); + /// let salad = world.entity(); + /// let alice = world.entity().add_id((likes, pizza)).add_id((likes, salad)); + /// + /// let q = world.query::<()>().with_second::(likes).build(); + /// + /// let mut count = 0; + /// let mut tgt_count = 0; + /// + /// q.each_iter(|mut it, row, _| { + /// let e = it.entity(row); + /// assert_eq!(e, alice); + /// + /// it.targets(0, |tgt| { + /// if tgt_count == 0 { + /// assert_eq!(tgt, pizza); + /// } + /// if tgt_count == 1 { + /// assert_eq!(tgt, salad); + /// } + /// tgt_count += 1; + /// }); + /// + /// count += 1; + /// }); + /// + /// assert_eq!(count, 1); + /// assert_eq!(tgt_count, 2); + /// ``` + /// + /// # See Also + /// + /// * C++ API: `iter::targets` + #[doc(alias = "iter::targets")] + pub fn targets(&mut self, index: i8, mut func: impl FnMut(EntityView)) { + ecs_assert!(!self.iter.table.is_null(), FlecsErrorCode::InvalidOperation); + ecs_assert!( + index < self.iter.field_count, + FlecsErrorCode::InvalidOperation + ); + ecs_assert!( + unsafe { sys::ecs_field_is_set(self.iter, index) }, + FlecsErrorCode::InvalidOperation + ); + let table_type = unsafe { sys::ecs_table_get_type(self.iter.table) }; + let table_record = unsafe { *self.iter.trs.add(index as usize) }; + let mut i = unsafe { (*table_record).index }; + let end = i + unsafe { (*table_record).count }; + while i < end { + let id = unsafe { *(*table_type).array.add(i as usize) }; + ecs_assert!( + ecs_is_pair(id), + FlecsErrorCode::InvalidParameter, + "field does not match a pair" + ); + let target = EntityView::new_from(self.world(), ecs_second(id)); + func(target); + i += 1; + } + } } impl<'a, P> TableIter<'a, true, P> diff --git a/flecs_ecs/src/core/world.rs b/flecs_ecs/src/core/world.rs index 0472849b..cf36edae 100644 --- a/flecs_ecs/src/core/world.rs +++ b/flecs_ecs/src/core/world.rs @@ -1976,12 +1976,12 @@ impl World { /// * C++ API: `world::get_ref` // #[doc(alias = "world::get_ref")] // #[inline(always)] - pub fn get_ref(&self) -> CachedRef + pub fn get_ref(&self) -> CachedRef where - T: ComponentId + DataComponent, - T::UnderlyingType: DataComponent, + T: ComponentOrPairId, + T::CastType: DataComponent, { - EntityView::new_from(self, T::id(self)).get_ref::() + EntityView::new_from(self, T::get_id(self)).get_ref::() } /// Get singleton entity for type. diff --git a/flecs_ecs/tests/flecs/entity_test.rs b/flecs_ecs/tests/flecs/entity_test.rs index b7d283ba..7ae2895e 100644 --- a/flecs_ecs/tests/flecs/entity_test.rs +++ b/flecs_ecs/tests/flecs/entity_test.rs @@ -1476,7 +1476,7 @@ fn entity_path_from_type_custom_sep() { assert_eq!( &grandchild.path_w_sep("_", "?").unwrap(), - "?flecs_common_test_Parent_child_grandchild" + "?flecs_common\\_test_Parent_child_grandchild" ); assert_eq!( &grandchild.path_from_id_w_sep(parent, "_", "::").unwrap(), diff --git a/flecs_ecs/tests/flecs/query_test.rs b/flecs_ecs/tests/flecs/query_test.rs index 8f59bda1..a4036386 100644 --- a/flecs_ecs/tests/flecs/query_test.rs +++ b/flecs_ecs/tests/flecs/query_test.rs @@ -119,3 +119,150 @@ fn query_each_sparse() { assert_eq!(p.y, 22); }); } + +#[test] +fn query_iter_targets() { + let world = World::new(); + + let likes = world.entity(); + let pizza = world.entity(); + let salad = world.entity(); + let alice = world.entity().add_id((likes, pizza)).add_id((likes, salad)); + + let q = world.query::<()>().with_second::(likes).build(); + + let mut count = 0; + let mut tgt_count = 0; + + q.each_iter(|mut it, row, _| { + let e = it.entity(row); + assert_eq!(e, alice); + + it.targets(0, |tgt| { + if tgt_count == 0 { + assert_eq!(tgt, pizza); + } + if tgt_count == 1 { + assert_eq!(tgt, salad); + } + tgt_count += 1; + }); + + count += 1; + }); + + assert_eq!(count, 1); + assert_eq!(tgt_count, 2); +} + +#[test] +fn query_iter_targets_second_field() { + let world = World::new(); + + let likes = world.entity(); + let pizza = world.entity(); + let salad = world.entity(); + let alice = world + .entity() + .add::() + .add_id((likes, pizza)) + .add_id((likes, salad)); + + let q = world + .query::<&Position>() + .with_second::(likes) + .build(); + + let mut count = 0; + let mut tgt_count = 0; + + q.each_iter(|mut it, row, _| { + let e = it.entity(row); + assert_eq!(e, alice); + + it.targets(1, |tgt| { + if tgt_count == 0 { + assert_eq!(tgt, pizza); + } + if tgt_count == 1 { + assert_eq!(tgt, salad); + } + tgt_count += 1; + }); + + count += 1; + }); + + assert_eq!(count, 1); + assert_eq!(tgt_count, 2); +} + +#[test] +#[should_panic] +#[cfg(debug_assertions)] +fn query_iter_targets_field_out_of_range() { + let world = World::new(); + + let likes = world.entity(); + let pizza = world.entity(); + let salad = world.entity(); + let alice = world.entity().add_id((likes, pizza)).add_id((likes, salad)); + + let q = world.query::<()>().with_second::(likes).build(); + + q.each_iter(|mut it, row, _| { + let e = it.entity(row); + assert_eq!(e, alice); + + it.targets(1, |_| {}); + }); +} + +#[test] +#[should_panic] +#[cfg(debug_assertions)] +fn query_iter_targets_field_not_a_pair() { + let world = World::new(); + + let likes = world.entity(); + let pizza = world.entity(); + let salad = world.entity(); + let alice = world + .entity() + .add::() + .add_id((likes, pizza)) + .add_id((likes, salad)); + + let q = world.query::<&Position>().build(); + + q.each_iter(|mut it, row, _| { + let e = it.entity(row); + assert_eq!(e, alice); + + it.targets(1, |_| {}); + }); +} + +#[test] +#[should_panic] +#[cfg(debug_assertions)] +fn query_iter_targets_field_not_set() { + let world = World::new(); + + let likes = world.entity(); + let alice = world.entity().add::(); + + let q = world + .query::<&Position>() + .with_second::(likes) + .optional() + .build(); + + q.each_iter(|mut it, row, _| { + let e = it.entity(row); + assert_eq!(e, alice); + assert!(!it.is_set(1)); + + it.targets(1, |_| {}); + }); +} diff --git a/flecs_ecs_sys/src/bindings.rs b/flecs_ecs_sys/src/bindings.rs index cfb7d0bc..eb7dbbfb 100644 --- a/flecs_ecs_sys/src/bindings.rs +++ b/flecs_ecs_sys/src/bindings.rs @@ -8,7 +8,7 @@ pub const FLECS_TERM_COUNT_MAX: u32 = 32; pub const FLECS_VERSION_MAJOR: u32 = 4; pub const FLECS_VERSION_MINOR: u32 = 0; -pub const FLECS_VERSION_PATCH: u32 = 1; +pub const FLECS_VERSION_PATCH: u32 = 3; pub const FLECS_HI_ID_RECORD_ID: u32 = 1024; pub const FLECS_SPARSE_PAGE_BITS: u32 = 12; pub const FLECS_ENTITY_PAGE_BITS: u32 = 12; @@ -97,6 +97,7 @@ pub const EcsQueryHasCacheable: u32 = 16777216; pub const EcsQueryIsCacheable: u32 = 33554432; pub const EcsQueryHasTableThisVar: u32 = 67108864; pub const EcsQueryCacheYieldEmptyTables: u32 = 134217728; +pub const EcsQueryNested: u32 = 268435456; pub const EcsTermMatchAny: u32 = 1; pub const EcsTermMatchAnySrc: u32 = 2; pub const EcsTermTransitive: u32 = 4; @@ -116,7 +117,8 @@ pub const EcsObserverIsMonitor: u32 = 4; pub const EcsObserverIsDisabled: u32 = 8; pub const EcsObserverIsParentDisabled: u32 = 16; pub const EcsObserverBypassQuery: u32 = 32; -pub const EcsObserverYieldOnDelete: u32 = 64; +pub const EcsObserverYieldOnCreate: u32 = 64; +pub const EcsObserverYieldOnDelete: u32 = 128; pub const EcsTableHasBuiltins: u32 = 2; pub const EcsTableIsPrefab: u32 = 4; pub const EcsTableHasIsA: u32 = 8; @@ -2975,6 +2977,10 @@ extern "C-unwind" { #[doc = "Return entity identifiers in world.\n This operation returns an array with all entity ids that exist in the world.\n Note that the returned array will change and may get invalidated as a result\n of entity creation & deletion.\n\n To iterate all alive entity ids, do:\n @code\n ecs_entities_t entities = ecs_get_entities(world);\n for (int i = 0; i < entities.alive_count; i ++) {\n ecs_entity_t id = entities.ids\\[i\\];\n }\n @endcode\n\n To iterate not-alive ids, do:\n @code\n for (int i = entities.alive_count + 1; i < entities.count; i ++) {\n ecs_entity_t id = entities.ids\\[i\\];\n }\n @endcode\n\n The returned array does not need to be freed. Mutating the returned array\n will return in undefined behavior (and likely crashes).\n\n @param world The world.\n @return Struct with entity id array."] pub fn ecs_get_entities(world: *const ecs_world_t) -> ecs_entities_t; } +extern "C-unwind" { + #[doc = "Get flags set on the world.\n This operation returns the internal flags (see api_flags.h) that are\n set on the world.\n\n @param world The world.\n @return Flags set on the world."] + pub fn ecs_world_get_flags(world: *const ecs_world_t) -> ecs_flags32_t; +} extern "C-unwind" { #[doc = "Begin frame.\n When an application does not use ecs_progress() to control the main loop, it\n can still use Flecs features such as FPS limiting and time measurements. This\n operation needs to be invoked whenever a new frame is about to get processed.\n\n Calls to ecs_frame_begin() must always be followed by ecs_frame_end().\n\n The function accepts a delta_time parameter, which will get passed to\n systems. This value is also used to compute the amount of time the function\n needs to sleep to ensure it does not exceed the target_fps, when it is set.\n When 0 is provided for delta_time, the time will be measured.\n\n This function should only be ran from the main thread.\n\n @param world The world.\n @param delta_time Time elapsed since the last frame.\n @return The provided delta_time, or measured time if 0 was provided."] pub fn ecs_frame_begin(world: *mut ecs_world_t, delta_time: f32) -> f32; @@ -3568,6 +3574,7 @@ extern "C-unwind" { sep: *const ::core::ffi::c_char, prefix: *const ::core::ffi::c_char, buf: *mut ecs_strbuf_t, + escape: bool, ); } extern "C-unwind" { @@ -3859,6 +3866,10 @@ extern "C-unwind" { #[doc = "Does query return one or more results.\n\n @param query The query.\n @return True if query matches anything, false if not."] pub fn ecs_query_is_true(query: *const ecs_query_t) -> bool; } +extern "C-unwind" { + #[doc = "Get query used to populate cache.\n This operation returns the query that is used to populate the query cache.\n For queries that are can be entirely cached, the returned query will be\n equivalent to the query passed to ecs_query_get_cache_query().\n\n @param query The query.\n @return The query used to populate the cache, NULL if query is not cached."] + pub fn ecs_query_get_cache_query(query: *const ecs_query_t) -> *const ecs_query_t; +} extern "C-unwind" { #[doc = "Send event.\n This sends an event to matching triggers & is the mechanism used by flecs\n itself to send `OnAdd`, `OnRemove`, etc events.\n\n Applications can use this function to send custom events, where a custom\n event can be any regular entity.\n\n Applications should not send builtin flecs events, as this may violate\n assumptions the code makes about the conditions under which those events are\n sent.\n\n Triggers are invoked synchronously. It is therefore safe to use stack-based\n data as event context, which can be set in the \"param\" member.\n\n @param world The world.\n @param desc Event parameters.\n\n @see ecs_enqueue()"] pub fn ecs_emit(world: *mut ecs_world_t, desc: *mut ecs_event_desc_t); @@ -4176,6 +4187,10 @@ extern "C-unwind" { tr_out: *mut *mut ecs_table_record_t, ) -> i32; } +extern "C-unwind" { + #[doc = "Remove all entities in a table. Does not deallocate table memory.\n Retaining table memory can be efficient when planning\n to refill the table with operations like ecs_bulk_init\n\n @param world The world.\n @param table The table to clear."] + pub fn ecs_table_clear_entities(world: *mut ecs_world_t, table: *mut ecs_table_t); +} extern "C-unwind" { #[doc = "Construct a value in existing storage\n\n @param world The world.\n @param type The type of the value to create.\n @param ptr Pointer to a value of type 'type'\n @return Zero if success, nonzero if failed."] pub fn ecs_value_init( @@ -4460,7 +4475,7 @@ pub const ecs_http_method_t_EcsHttpDelete: ecs_http_method_t = 3; pub const ecs_http_method_t_EcsHttpOptions: ecs_http_method_t = 4; pub const ecs_http_method_t_EcsHttpMethodUnsupported: ecs_http_method_t = 5; #[doc = "Supported request methods."] -pub type ecs_http_method_t = ::core::ffi::c_int; +pub type ecs_http_method_t = ::core::ffi::c_uint; #[doc = "An HTTP request."] #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -6515,6 +6530,10 @@ extern "C" { #[doc = "< Component id for EcsDocDescription."] pub static FLECS_IDEcsDocDescriptionID_: ecs_entity_t; } +extern "C" { + #[doc = "Tag for adding a UUID to entities.\n Added to an entity as (EcsDocDescription, EcsUuid) by ecs_doc_set_uuid()."] + pub static EcsDocUuid: ecs_entity_t; +} extern "C" { #[doc = "Tag for adding brief descriptions to entities.\n Added to an entity as (EcsDocDescription, EcsBrief) by ecs_doc_set_brief()."] pub static EcsDocBrief: ecs_entity_t; @@ -6537,6 +6556,14 @@ extern "C" { pub struct EcsDocDescription { pub value: *mut ::core::ffi::c_char, } +extern "C-unwind" { + #[doc = "Add UUID to entity.\n Associate entity with an (external) UUID.\n\n @param world The world.\n @param entity The entity to which to add the UUID.\n @param uuid The UUID to add.\n\n @see ecs_doc_get_uuid()\n @see flecs::doc::set_uuid()\n @see flecs::entity_builder::set_doc_uuid()"] + pub fn ecs_doc_set_uuid( + world: *mut ecs_world_t, + entity: ecs_entity_t, + uuid: *const ::core::ffi::c_char, + ); +} extern "C-unwind" { #[doc = "Add human-readable name to entity.\n Contrary to entity names, human readable names do not have to be unique and\n can contain special characters used in the query language like '*'.\n\n @param world The world.\n @param entity The entity to which to add the name.\n @param name The name to add.\n\n @see ecs_doc_get_name()\n @see flecs::doc::set_name()\n @see flecs::entity_builder::set_doc_name()"] pub fn ecs_doc_set_name( @@ -6577,6 +6604,13 @@ extern "C-unwind" { color: *const ::core::ffi::c_char, ); } +extern "C-unwind" { + #[doc = "Get UUID from entity.\n @param world The world.\n @param entity The entity from which to get the UUID.\n @return The UUID.\n\n @see ecs_doc_set_uuid()\n @see flecs::doc::get_uuid()\n @see flecs::entity_view::get_doc_uuid()"] + pub fn ecs_doc_get_uuid( + world: *const ecs_world_t, + entity: ecs_entity_t, + ) -> *const ::core::ffi::c_char; +} extern "C-unwind" { #[doc = "Get human readable name from entity.\n If entity does not have an explicit human readable name, this operation will\n return the entity name.\n\n To test if an entity has a human readable name, use:\n\n @code\n ecs_has_pair(world, e, ecs_id(EcsDocDescription), EcsName);\n @endcode\n\n Or in C++:\n\n @code\n e.has(flecs::Name);\n @endcode\n\n @param world The world.\n @param entity The entity from which to get the name.\n @return The name.\n\n @see ecs_doc_set_name()\n @see flecs::doc::get_name()\n @see flecs::entity_view::get_doc_name()"] pub fn ecs_doc_get_name( diff --git a/flecs_ecs_sys/src/flecs.c b/flecs_ecs_sys/src/flecs.c index 9b31a0d4..29bb0027 100644 --- a/flecs_ecs_sys/src/flecs.c +++ b/flecs_ecs_sys/src/flecs.c @@ -927,12 +927,13 @@ typedef struct ecs_store_t { /* Records cache */ ecs_vec_t records; - /* Stack of ids being deleted. */ + /* Stack of ids being deleted during cleanup action. */ ecs_vec_t marked_ids; /* vector */ - - /* Entity ids associated with depth (for flat hierarchies) */ - ecs_vec_t depth_ids; - ecs_map_t entity_to_depth; /* What it says */ + + /* Components deleted during cleanup action. Used to delay cleaning up of + * type info so it's guaranteed that this data is available while the + * storage is cleaning up tables. */ + ecs_vec_t deleted_components; /* vector */ } ecs_store_t; /* fini actions */ @@ -3698,7 +3699,19 @@ void flecs_on_component(ecs_iter_t *it) { flecs_assert_relation_unused(world, e, ecs_id(EcsComponent)); } } else if (it->event == EcsOnRemove) { - flecs_type_info_free(world, e); + #ifdef FLECS_DEBUG + if (ecs_should_log(0)) { + char *path = ecs_get_path(world, e); + ecs_trace("unregistering component '%s'", path); + ecs_os_free(path); + } + #endif + if (!ecs_vec_count(&world->store.marked_ids)) { + flecs_type_info_free(world, e); + } else { + ecs_vec_append_t(&world->allocator, + &world->store.deleted_components, ecs_entity_t)[0] = e; + } } } } @@ -5500,7 +5513,39 @@ void flecs_instantiate( { ecs_record_t *record = flecs_entities_get_any(world, base); ecs_table_t *base_table = record->table; - if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { + if (!base_table) { + return; + } + + /* If prefab has union relationships, also set them on instance */ + if (base_table->flags & EcsTableHasUnion) { + const ecs_entity_t *entities = ecs_table_entities(table); + ecs_id_record_t *union_idr = flecs_id_record_get(world, + ecs_pair(EcsWildcard, EcsUnion)); + ecs_assert(union_idr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = flecs_id_record_get_table( + union_idr, base_table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i = 0, j, union_count = 0; + do { + ecs_id_t id = base_table->type.array[i]; + if (ECS_PAIR_SECOND(id) == EcsUnion) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ecs_get_target(world, base, rel, 0); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + for (j = row; j < (row + count); j ++) { + ecs_add_pair(world, entities[j], rel, tgt); + } + + union_count ++; + } + + i ++; + } while (union_count < tr->count); + } + + if (!(base_table->flags & EcsTableIsPrefab)) { /* Don't instantiate children from base entities that aren't prefabs */ return; } @@ -7735,7 +7780,7 @@ void flecs_on_delete( /* Cleanup can happen recursively. If a cleanup action is already in * progress, only append ids to the marked_ids. The topmost cleanup * frame will handle the actual cleanup. */ - int32_t count = ecs_vec_count(&world->store.marked_ids); + int32_t i, count = ecs_vec_count(&world->store.marked_ids); /* Make sure we're evaluating a consistent list of non-empty tables */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); @@ -7760,7 +7805,7 @@ void flecs_on_delete( /* Verify deleted ids are no longer in use */ #ifdef FLECS_DEBUG ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); - int32_t i; count = ecs_vec_count(&world->store.marked_ids); + count = ecs_vec_count(&world->store.marked_ids); for (i = 0; i < count; i ++) { ecs_assert(!ecs_id_in_use(world, ids[i].id), ECS_INTERNAL_ERROR, NULL); @@ -7771,6 +7816,16 @@ void flecs_on_delete( /* Ids are deleted, clear stack */ ecs_vec_clear(&world->store.marked_ids); + /* If any components got deleted, cleanup type info. Delaying this + * ensures that type info remains available during cleanup. */ + count = ecs_vec_count(&world->store.deleted_components); + ecs_entity_t *comps = ecs_vec_first(&world->store.deleted_components); + for (i = 0; i < count; i ++) { + flecs_type_info_free(world, comps[i]); + } + + ecs_vec_clear(&world->store.deleted_components); + ecs_log_pop_2(); } } @@ -9466,13 +9521,13 @@ void ecs_id_str_buf( } ecs_strbuf_appendch(buf, '('); - ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); + ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf, false); ecs_strbuf_appendch(buf, ','); - ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); + ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf, false); ecs_strbuf_appendch(buf, ')'); } else { ecs_entity_t e = id & ECS_COMPONENT_MASK; - ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); + ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf, false); } error: @@ -9544,7 +9599,7 @@ char* ecs_entity_str( ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); + ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf, false); ecs_strbuf_appendlit(&buf, " ["); const ecs_type_t *type = ecs_get_type(world, entity); @@ -10297,7 +10352,8 @@ bool flecs_path_append( ecs_entity_t child, const char *sep, const char *prefix, - ecs_strbuf_t *buf) + ecs_strbuf_t *buf, + bool escape) { flecs_poly_assert(world, ecs_world_t); ecs_assert(sep[0] != 0, ECS_INVALID_PARAMETER, NULL); @@ -10318,7 +10374,7 @@ bool flecs_path_append( if (cur) { ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL); if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { - flecs_path_append(world, parent, cur, sep, prefix, buf); + flecs_path_append(world, parent, cur, sep, prefix, buf, escape); if (!sep[1]) { ecs_strbuf_appendch(buf, sep[0]); } else { @@ -10343,7 +10399,44 @@ bool flecs_path_append( } if (name) { - ecs_strbuf_appendstrn(buf, name, name_len); + /* Check if we need to escape separator character */ + const char *sep_in_name = NULL; + if (!sep[1]) { + sep_in_name = strchr(name, sep[0]); + } + + if (sep_in_name || escape) { + const char *name_ptr; + char ch; + for (name_ptr = name; (ch = name_ptr[0]); name_ptr ++) { + char esc[3]; + if (ch != sep[0]) { + if (escape) { + flecs_chresc(esc, ch, '\"'); + ecs_strbuf_appendch(buf, esc[0]); + if (esc[1]) { + ecs_strbuf_appendch(buf, esc[1]); + } + } else { + ecs_strbuf_appendch(buf, ch); + } + } else { + if (!escape) { + ecs_strbuf_appendch(buf, '\\'); + ecs_strbuf_appendch(buf, sep[0]); + } else { + ecs_strbuf_appendlit(buf, "\\\\"); + flecs_chresc(esc, ch, '\"'); + ecs_strbuf_appendch(buf, esc[0]); + if (esc[1]) { + ecs_strbuf_appendch(buf, esc[1]); + } + } + } + } + } else { + ecs_strbuf_appendstrn(buf, name, name_len); + } } else { ecs_strbuf_appendch(buf, '#'); ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); @@ -10572,7 +10665,8 @@ void ecs_get_path_w_sep_buf( ecs_entity_t child, const char *sep, const char *prefix, - ecs_strbuf_t *buf) + ecs_strbuf_t *buf, + bool escape) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); @@ -10593,7 +10687,7 @@ void ecs_get_path_w_sep_buf( } if (!child || parent != child) { - flecs_path_append(world, parent, child, sep, prefix, buf); + flecs_path_append(world, parent, child, sep, prefix, buf, escape); } else { ecs_strbuf_appendstrn(buf, "", 0); } @@ -10610,7 +10704,7 @@ char* ecs_get_path_w_sep( const char *prefix) { ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); + ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf, false); return ecs_strbuf_get(&buf); } @@ -13740,6 +13834,8 @@ void flecs_emit( int32_t ider_i, ider_count = 0; bool is_pair = ECS_IS_PAIR(id); void *override_ptr = NULL; + bool override_base_added = false; + ecs_table_record_t *base_tr = NULL; ecs_entity_t base = 0; bool id_can_override = can_override; ecs_flags64_t id_bit = 1llu << i; @@ -13793,7 +13889,6 @@ void flecs_emit( /* Initialize overridden components with value from base */ ti = idr->type_info; if (ti) { - ecs_table_record_t *base_tr = NULL; int32_t base_column = ecs_search_relation(world, table, 0, id, EcsIsA, EcsUp, &base, NULL, &base_tr); if (base_column != -1) { @@ -13811,6 +13906,26 @@ void flecs_emit( override_ptr = base_table->data.columns[base_column].data; override_ptr = ECS_ELEM(override_ptr, ti->size, base_row); } + + /* For ids with override policy, check if base was added + * in same operation. This will determine later on + * whether we need to emit an OnSet event. */ + if (!(idr->flags & + (EcsIdOnInstantiateInherit|EcsIdOnInstantiateDontInherit))) { + int32_t base_i; + for (base_i = 0; base_i < id_count; base_i ++) { + ecs_id_t base_id = id_array[base_i]; + if (!ECS_IS_PAIR(base_id)) { + continue; + } + if (ECS_PAIR_FIRST(base_id) != EcsIsA) { + continue; + } + if (ECS_PAIR_SECOND(base_id) == (uint32_t)base) { + override_base_added = true; + } + } + } } } } @@ -13849,7 +13964,8 @@ void flecs_emit( if (count) { storage_i = tr->column; bool is_sparse = idr->flags & EcsIdIsSparse; - if (storage_i != -1 || is_sparse) { + + if (!ecs_id_is_wildcard(id) && (storage_i != -1 || is_sparse)) { void *ptr; ecs_size_t size = idr->type_info->size; @@ -13865,7 +13981,7 @@ void flecs_emit( ptr = ECS_ELEM(c->data, size, offset); } - /* safe, owned by observer */ + /* Safe, owned by observer */ ECS_CONST_CAST(int32_t*, it.sizes)[0] = size; if (override_ptr) { @@ -13874,7 +13990,27 @@ void flecs_emit( * with the value of the overridden component. */ flecs_override_copy(world, table, tr, ti, ptr, override_ptr, offset, count); - } else if (er_onset) { + + /* If the base for this component got added in the same + * operation, generate an OnSet event as this is the + * first time this value is observed for the entity. */ + if (override_base_added) { + ecs_event_id_record_t *iders_set[5] = {0}; + int32_t ider_set_i, ider_set_count = + flecs_event_observers_get(er_onset, id, iders_set); + for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { + ecs_event_id_record_t *ider = iders_set[ider_set_i]; + flecs_observers_invoke( + world, &ider->self, &it, table, 0); + ecs_assert(it.event_cur == evtx, + ECS_INTERNAL_ERROR, NULL); + flecs_observers_invoke( + world, &ider->self_up, &it, table, 0); + ecs_assert(it.event_cur == evtx, + ECS_INTERNAL_ERROR, NULL); + } + } + } else if (er_onset && it.other_table) { /* If an override was removed, this re-exposes the * overridden component. Because this causes the actual * (now inherited) value of the component to change, an @@ -13887,6 +14023,9 @@ void flecs_emit( /* Set the source temporarily to the base and base * component pointer. */ it.sources[0] = base; + it.trs[0] = base_tr; + it.up_fields = 1; + for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { ecs_event_id_record_t *ider = iders_set[ider_set_i]; flecs_observers_invoke( @@ -13900,6 +14039,7 @@ void flecs_emit( } it.sources[0] = 0; + it.trs[0] = tr; } } } @@ -14452,8 +14592,7 @@ void flecs_observers_invoke( while (ecs_map_next(&oit)) { ecs_observer_t *o = ecs_map_ptr(&oit); ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL); - flecs_uni_observer_invoke( - world, o, it, table, trav); + flecs_uni_observer_invoke(world, o, it, table, trav); } ecs_table_unlock(it->world, table); @@ -14566,8 +14705,6 @@ void flecs_multi_observer_invoke( ecs_table_unlock(it->world, table); flecs_stage_set_system(world->stages[0], old_system); - - return; } else { /* While the observer query was strictly speaking evaluated, it's more * useful to measure how often the observer was actually invoked. */ @@ -14578,6 +14715,40 @@ void flecs_multi_observer_invoke( return; } +static +void flecs_multi_observer_invoke_no_query( + ecs_iter_t *it) +{ + ecs_observer_t *o = it->ctx; + flecs_poly_assert(o, ecs_observer_t); + + ecs_world_t *world = it->real_world; + ecs_table_t *table = it->table; + ecs_iter_t user_it = *it; + + user_it.ctx = o->ctx; + user_it.callback_ctx = o->callback_ctx; + user_it.run_ctx = o->run_ctx; + user_it.param = it->param; + user_it.callback = o->callback; + user_it.system = o->entity; + user_it.event = it->event; + + ecs_entity_t old_system = flecs_stage_set_system( + world->stages[0], o->entity); + ecs_table_lock(it->world, table); + + if (o->run) { + user_it.next = flecs_default_next_callback; + o->run(&user_it); + } else { + user_it.callback(&user_it); + } + + ecs_table_unlock(it->world, table); + flecs_stage_set_system(world->stages[0], old_system); +} + /* For convenience, so applications can (in theory) use a single run callback * that uses ecs_iter_next to iterate results */ bool flecs_default_next_callback(ecs_iter_t *it) { @@ -14619,7 +14790,7 @@ void flecs_observer_yield_existing( { ecs_run_action_t run = o->run; if (!run) { - run = flecs_multi_observer_invoke; + run = flecs_multi_observer_invoke_no_query; } ecs_run_aperiodic(world, EcsAperiodicEmptyTables); @@ -14713,6 +14884,9 @@ int flecs_observer_add_child( ecs_observer_t *o, const ecs_observer_desc_t *child_desc) { + ecs_assert(child_desc->query.flags & EcsQueryNested, + ECS_INTERNAL_ERROR, NULL); + ecs_observer_t *child_observer = flecs_observer_init( world, 0, child_desc); if (!child_observer) { @@ -14758,11 +14932,13 @@ int flecs_multi_observer_init( child_desc.run_ctx = NULL; child_desc.run_ctx_free = NULL; child_desc.yield_existing = false; + child_desc.flags_ &= ~(EcsObserverYieldOnCreate|EcsObserverYieldOnDelete); ecs_os_zeromem(&child_desc.entity); ecs_os_zeromem(&child_desc.query.terms); ecs_os_zeromem(&child_desc.query); - ecs_os_memcpy_n(child_desc.events, o->events, - ecs_entity_t, o->event_count); + ecs_os_memcpy_n(child_desc.events, o->events, ecs_entity_t, o->event_count); + + child_desc.query.flags |= EcsQueryNested; int i, term_count = query->term_count; bool optional_only = query->flags & EcsQueryMatchThis; @@ -14876,7 +15052,10 @@ int flecs_multi_observer_init( term->src.id = EcsThis | EcsIsVariable | EcsSelf; term->second.id = 0; } else if (term->oper == EcsOptional) { - continue; + if (only_table_events) { + /* For table events optional terms aren't necessary */ + continue; + } } if (flecs_observer_add_child(world, o, &child_desc)) { @@ -14976,7 +15155,10 @@ ecs_observer_t* flecs_observer_init( impl->term_index = desc->term_index_; impl->flags = desc->flags_; - bool yield_on_create = false; + ecs_check(!(desc->yield_existing && + (desc->flags_ & (EcsObserverYieldOnCreate|EcsObserverYieldOnDelete))), + ECS_INVALID_PARAMETER, + "cannot set yield_existing and YieldOn* flags at the same time"); /* Check if observer is monitor. Monitors are created as multi observers * since they require pre/post checking of the filter to test if the @@ -14997,8 +15179,8 @@ ecs_observer_t* flecs_observer_init( o->event_count ++; impl->flags |= EcsObserverIsMonitor; if (desc->yield_existing) { + impl->flags |= EcsObserverYieldOnCreate; impl->flags |= EcsObserverYieldOnDelete; - yield_on_create = true; } } else { o->events[i] = event; @@ -15006,7 +15188,7 @@ ecs_observer_t* flecs_observer_init( if (event == EcsOnRemove) { impl->flags |= EcsObserverYieldOnDelete; } else { - yield_on_create = true; + impl->flags |= EcsObserverYieldOnCreate; } } } @@ -15042,7 +15224,7 @@ ecs_observer_t* flecs_observer_init( } } - if (yield_on_create) { + if (impl->flags & EcsObserverYieldOnCreate) { flecs_observer_yield_existing(world, o, false); } @@ -17712,13 +17894,17 @@ const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 114; const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 115; const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 116; const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 117; +const ecs_entity_t EcsDocUuid = FLECS_HI_COMPONENT_ID + 118; #endif /* REST module components */ #ifdef FLECS_REST -const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 118; +const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 119; #endif +/* Max static id: + * #define EcsFirstUserEntityId (FLECS_HI_COMPONENT_ID + 128) */ + /* Default lookup path */ static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; @@ -18114,8 +18300,7 @@ void flecs_init_store( ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &world->store.records, ecs_table_record_t, 0); ecs_vec_init_t(a, &world->store.marked_ids, ecs_marked_id_t, 0); - ecs_vec_init_t(a, &world->store.depth_ids, ecs_entity_t, 0); - ecs_map_init(&world->store.entity_to_depth, &world->allocator); + ecs_vec_init_t(a, &world->store.deleted_components, ecs_entity_t, 0); /* Initialize entity index */ flecs_entities_init(world); @@ -18130,7 +18315,7 @@ void flecs_init_store( /* Initialize root table */ flecs_init_root_table(world); - /* Initilaize observer sparse set */ + /* Initialize observer sparse set */ flecs_sparse_init_t(&world->store.observers, a, &world->allocators.sparse_chunk, ecs_observer_impl_t); } @@ -18173,45 +18358,70 @@ void flecs_fini_root_tables( ecs_id_record_t *idr, bool fini_targets) { - ecs_table_cache_iter_t it; + ecs_stage_t *stage0 = world->stages[0]; + bool finished = false; + const ecs_size_t MAX_DEFERRED_DELETE_QUEUE_SIZE = 4096; + while (!finished) { + ecs_table_cache_iter_t it; + ecs_size_t queue_size = 0; + finished = true; - bool has_roots = flecs_table_cache_iter(&idr->cache, &it); - ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); - (void)has_roots; + bool has_roots = flecs_table_cache_iter(&idr->cache, &it); + ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); + (void)has_roots; - const ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - if (table->flags & EcsTableHasBuiltins) { - continue; /* Query out modules */ - } + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (table->flags & EcsTableHasBuiltins) { + continue; /* Query out modules */ + } - int32_t i, count = ecs_table_count(table); - const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + const ecs_entity_t *entities = ecs_table_entities(table); - if (fini_targets) { - /* Only delete entities that are used as pair target. Iterate - * backwards to minimize moving entities around in table. */ - for (i = count - 1; i >= 0; i --) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); - if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { - ecs_delete(world, entities[i]); + if (fini_targets) { + /* Only delete entities that are used as pair target. Iterate + * backwards to minimize moving entities around in table. */ + for (i = count - 1; i >= 0; i --) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); + if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { + ecs_delete(world, entities[i]); + queue_size++; + /* Flush the queue before it grows too big: */ + if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) { + finished = false; + break; /* restart iteration */ + } + } } - } - } else { - /* Delete remaining entities that are not in use (added to another - * entity). This limits table moves during cleanup and delays - * cleanup of tags. */ - for (i = count - 1; i >= 0; i --) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); - if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) { - ecs_delete(world, entities[i]); + } else { + /* Delete remaining entities that are not in use (added to another + * entity). This limits table moves during cleanup and delays + * cleanup of tags. */ + for (i = count - 1; i >= 0; i --) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); + if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) { + ecs_delete(world, entities[i]); + queue_size++; + /* Flush the queue before it grows too big: */ + if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) { + finished = false; + break; /* restart iteration */ + } + } } } + if(!finished) { + /* flush queue and restart iteration */ + flecs_defer_end(world, stage0); + flecs_defer_begin(world, stage0); + break; + } } } } @@ -18249,11 +18459,15 @@ void flecs_fini_store(ecs_world_t *world) { ECS_INTERNAL_ERROR, NULL); flecs_sparse_fini(&world->store.observers); + ecs_assert(ecs_vec_count(&world->store.marked_ids) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vec_count(&world->store.deleted_components) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &world->store.records, ecs_table_record_t); ecs_vec_fini_t(a, &world->store.marked_ids, ecs_marked_id_t); - ecs_vec_fini_t(a, &world->store.depth_ids, ecs_entity_t); - ecs_map_fini(&world->store.entity_to_depth); + ecs_vec_fini_t(a, &world->store.deleted_components, ecs_entity_t); } static @@ -19307,6 +19521,9 @@ void flecs_type_info_fini( ecs_os_free(ECS_CONST_CAST(char*, ti->name)); ti->name = NULL; } + + ti->size = 0; + ti->alignment = 0; } void flecs_type_info_free( @@ -19764,6 +19981,13 @@ ecs_entities_t ecs_get_entities( return result; } +ecs_flags32_t ecs_world_get_flags( + const ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); + return world->flags; +} + /** * @file addons/alerts.c * @brief Alerts addon. @@ -20752,6 +20976,14 @@ void flecs_doc_set( } } +void ecs_doc_set_uuid( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + flecs_doc_set(world, entity, EcsDocUuid, name); +} + void ecs_doc_set_name( ecs_world_t *world, ecs_entity_t entity, @@ -20792,6 +21024,19 @@ void ecs_doc_set_color( flecs_doc_set(world, entity, EcsDocColor, color); } +const char* ecs_doc_get_uuid( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocUuid); + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} + const char* ecs_doc_get_name( const ecs_world_t *world, ecs_entity_t entity) @@ -20954,6 +21199,7 @@ void FlecsDocImport( ecs_set_name_prefix(world, "EcsDoc"); flecs_bootstrap_component(world, EcsDocDescription); + flecs_bootstrap_tag(world, EcsDocUuid); flecs_bootstrap_tag(world, EcsDocBrief); flecs_bootstrap_tag(world, EcsDocDetail); flecs_bootstrap_tag(world, EcsDocLink); @@ -21074,12 +21320,9 @@ char* ecs_cpp_get_symbol_name( const char *type_name, size_t len) { - // Symbol is same as name, but with '::' replaced with '.' - ecs_os_strcpy(symbol_name, type_name); - - char *ptr; + const char *ptr; size_t i; - for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { + for (i = 0, ptr = type_name; i < len && *ptr; i ++, ptr ++) { if (*ptr == ':') { symbol_name[i] = '.'; ptr ++; @@ -22660,7 +22903,7 @@ http_conn_res_t http_init_connection( } static -void http_accept_connections( +int http_accept_connections( ecs_http_server_t* srv, const struct sockaddr* addr, ecs_size_t addr_len) @@ -22674,7 +22917,7 @@ void http_accept_connections( if (result) { ecs_warn("http: WSAStartup failed with GetLastError = %d\n", GetLastError()); - return; + return 0; } } else { http_close(&testsocket); @@ -22685,6 +22928,8 @@ void http_accept_connections( char addr_host[256]; char addr_port[20]; + int ret = 0; /* 0 = ok, 1 = port occupied */ + ecs_http_socket_t sock = HTTP_SOCKET_INVALID; ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); @@ -22727,8 +22972,15 @@ void http_accept_connections( result = http_bind(sock, addr, addr_len); if (result) { - ecs_err("http: failed to bind to '%s:%s': %s", - addr_host, addr_port, ecs_os_strerror(errno)); + if (errno == EADDRINUSE) { + ret = 1; + ecs_warn("http: address '%s:%s' in use, retrying with port %u", + addr_host, addr_port, srv->port + 1); + } else { + ecs_err("http: failed to bind to '%s:%s': %s", + addr_host, addr_port, ecs_os_strerror(errno)); + } + ecs_os_mutex_unlock(srv->lock); goto done; } @@ -22780,6 +23032,8 @@ void http_accept_connections( ecs_trace("http: no longer accepting connections on '%s:%s'", addr_host, addr_port); + + return ret; } static @@ -22788,6 +23042,9 @@ void* http_server_thread(void* arg) { struct sockaddr_in addr; ecs_os_zeromem(&addr); addr.sin_family = AF_INET; + + int retries = 0; +retry: addr.sin_port = htons(srv->port); if (!srv->ipaddr) { @@ -22796,7 +23053,18 @@ void* http_server_thread(void* arg) { inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); } - http_accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); + if (http_accept_connections( + srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)) == 1) + { + srv->port ++; + retries ++; + if (retries < 10) { + goto retry; + } else { + ecs_err("http: failed to connect (retried 10 times)"); + } + } + return NULL; } @@ -23887,7 +24155,7 @@ int ecs_log_get_level(void) { int ecs_log_set_level( int level) { - int prev = level; + int prev = ecs_os_api.log_level_; ecs_os_api.log_level_ = level; return prev; } @@ -26071,7 +26339,7 @@ void flecs_system_stats_to_json( { ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":\""); - ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply); + ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply, true); ecs_strbuf_appendch(reply, '"'); bool disabled = ecs_has_id(world, system, EcsDisabled); @@ -28667,7 +28935,6 @@ void flecs_bfree( flecs_bfree_w_dbg_info(ba, memory, NULL); } -FLECS_API void flecs_bfree_w_dbg_info( ecs_block_allocator_t *ba, void *memory, @@ -31536,6 +31803,7 @@ bool flecs_switch_set( if (!hdr[0]) { hdr[0] = (uint64_t)element; + node->next = 0; } else { ecs_switch_node_t *head = flecs_switch_get_node(sw, (uint32_t)hdr[0]); ecs_assert(head->prev == 0, ECS_INTERNAL_ERROR, NULL); @@ -31543,6 +31811,7 @@ bool flecs_switch_set( node->next = (uint32_t)hdr[0]; hdr[0] = (uint64_t)element; + ecs_assert(node->next != element, ECS_INTERNAL_ERROR, NULL); } node->prev = 0; @@ -32140,6 +32409,27 @@ int flecs_query_create_cache( ecs_os_memcpy_n(impl->cache->field_map, field_map, int8_t, dst_count); } + } else { + /* Check if query has features that are unsupported for uncached */ + ecs_assert(q->cache_kind == EcsQueryCacheNone, ECS_INTERNAL_ERROR, NULL); + + if (!(q->flags & EcsQueryNested)) { + /* If uncached query is not create to populate a cached query, it + * should not have cascade modifiers */ + int32_t i, count = q->term_count; + ecs_term_t *terms = q->terms; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->src.id & EcsCascade) { + char *query_str = ecs_query_str(q); + ecs_err( + "cascade is unsupported for uncached query\n %s", + query_str); + ecs_os_free(query_str); + goto error; + } + } + } } return 0; @@ -32438,6 +32728,17 @@ int32_t ecs_query_match_count( } } +const ecs_query_t* ecs_query_get_cache_query( + const ecs_query_t *q) +{ + ecs_query_impl_t *impl = flecs_query_impl(q); + if (!impl->cache) { + return NULL; + } else { + return impl->cache->query; + } +} + /** * @file query/util.c * @brief Query utilities. @@ -33012,8 +33313,8 @@ void flecs_term_to_buf( if (term->second.id & EcsIsEntity) { if (term->second.id != 0) { - ecs_get_path_w_sep_buf( - world, 0, ECS_TERM_REF_ID(&term->second), ".", NULL, buf); + ecs_get_path_w_sep_buf(world, 0, ECS_TERM_REF_ID(&term->second), + ".", NULL, buf, false); } } else { if (term->second.id & EcsIsVariable) { @@ -33976,6 +34277,36 @@ int flecs_term_finalize( } } + if (term->first.id & EcsCascade) { + flecs_query_validator_error(ctx, + "cascade modifier invalid for term.first"); + return -1; + } + + if (term->second.id & EcsCascade) { + flecs_query_validator_error(ctx, + "cascade modifier invalid for term.second"); + return -1; + } + + if (term->first.id & EcsDesc) { + flecs_query_validator_error(ctx, + "desc modifier invalid for term.first"); + return -1; + } + + if (term->second.id & EcsDesc) { + flecs_query_validator_error(ctx, + "desc modifier invalid for term.second"); + return -1; + } + + if (term->src.id & EcsDesc && !(term->src.id & EcsCascade)) { + flecs_query_validator_error(ctx, + "desc modifier invalid without cascade"); + return -1; + } + if (term->src.id & EcsCascade) { /* Cascade always implies up traversal */ term->src.id |= EcsUp; @@ -36230,7 +36561,7 @@ void flecs_table_init_columns( int16_t *s2t = &table->column_map[ids_count]; for (i = 0; i < ids_count; i ++) { - ecs_id_t id = table->type.array[i]; + ecs_id_t id = ids[i]; ecs_table_record_t *tr = &records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; const ecs_type_info_t *ti = idr->type_info; @@ -36250,17 +36581,21 @@ void flecs_table_init_columns( table->component_map[id] = flecs_ito(int16_t, cur + 1); } - if (ECS_IS_PAIR(ids[i])) { - ecs_table_record_t *wc_tr = flecs_id_record_get_table( - idr->parent, table); - if (wc_tr->index == tr->index) { - wc_tr->column = tr->column; - } - } - table->flags |= flecs_type_info_flags(ti); cur ++; } + + int32_t record_count = table->_->record_count; + for (; i < record_count; i ++) { + ecs_table_record_t *tr = &records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_id_t id = idr->id; + + if (ecs_id_is_wildcard(id)) { + ecs_table_record_t *first_tr = &records[tr->index]; + tr->column = first_tr->column; + } + } } /* Initialize table storage */ @@ -36961,7 +37296,8 @@ void flecs_table_fini_data( ecs_table_t *table, bool do_on_remove, bool is_delete, - bool deactivate) + bool deactivate, + bool deallocate) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); @@ -36974,36 +37310,51 @@ void flecs_table_fini_data( flecs_table_dtor_all(world, table, 0, count, is_delete); } - ecs_column_t *columns = table->data.columns; - if (columns) { - int32_t c, column_count = table->column_count; - for (c = 0; c < column_count; c ++) { - ecs_column_t *column = &columns[c]; - ecs_vec_t v = ecs_vec_from_column(column, table, column->ti->size); - ecs_vec_fini(&world->allocator, &v, column->ti->size); - column->data = NULL; - } + if (deallocate) { + ecs_column_t *columns = table->data.columns; + if (columns) { + int32_t c, column_count = table->column_count; + for (c = 0; c < column_count; c ++) { + ecs_column_t *column = &columns[c]; + ecs_vec_t v = ecs_vec_from_column(column, table, column->ti->size); + ecs_vec_fini(&world->allocator, &v, column->ti->size); + column->data = NULL; + } - flecs_wfree_n(world, ecs_column_t, table->column_count, columns); - table->data.columns = NULL; + flecs_wfree_n(world, ecs_column_t, table->column_count, columns); + table->data.columns = NULL; + } } ecs_table__t *meta = table->_; ecs_bitset_t *bs_columns = meta->bs_columns; if (bs_columns) { int32_t c, column_count = meta->bs_count; - for (c = 0; c < column_count; c ++) { - flecs_bitset_fini(&bs_columns[c]); + if (deallocate) { + for (c = 0; c < column_count; c ++) { + flecs_bitset_fini(&bs_columns[c]); + } + } + else { + for (c = 0; c < column_count; c++) { + bs_columns[c].count = 0; + } + } + + if (deallocate) { + flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); + meta->bs_columns = NULL; } - flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); - meta->bs_columns = NULL; } - ecs_vec_t v = ecs_vec_from_entities(table); - ecs_vec_fini_t(&world->allocator, &v, ecs_entity_t); - table->data.entities = NULL; + if (deallocate) { + ecs_vec_t v = ecs_vec_from_entities(table); + ecs_vec_fini_t(&world->allocator, &v, ecs_entity_t); + table->data.entities = NULL; + table->data.size = 0; + } + table->data.count = 0; - table->data.size = 0; if (deactivate && count) { flecs_table_set_empty(world, table); @@ -37019,28 +37370,36 @@ const ecs_entity_t* ecs_table_entities( return table->data.entities; } -/* Cleanup, no OnRemove, clear entity index, deactivate table */ +/* Cleanup, no OnRemove, delete from entity index, deactivate table, retain allocations */ +void ecs_table_clear_entities( + ecs_world_t* world, + ecs_table_t* table) +{ + flecs_table_fini_data(world, table, true, true, true, false); +} + +/* Cleanup, no OnRemove, clear entity index, deactivate table, free allocations */ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table) { - flecs_table_fini_data(world, table, false, false, true); + flecs_table_fini_data(world, table, false, false, true, true); } -/* Cleanup, run OnRemove, clear entity index, deactivate table */ +/* Cleanup, run OnRemove, clear entity index, deactivate table, free allocations */ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table) { - flecs_table_fini_data(world, table, true, false, true); + flecs_table_fini_data(world, table, true, false, true, true); } -/* Cleanup, run OnRemove, delete from entity index, deactivate table */ +/* Cleanup, run OnRemove, delete from entity index, deactivate table, free allocations */ void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table) { - flecs_table_fini_data(world, table, true, true, true); + flecs_table_fini_data(world, table, true, true, true, true); } /* Unset all components in table. This function is called before a table is @@ -37085,7 +37444,7 @@ void flecs_table_fini( world->info.empty_table_count -= (ecs_table_count(table) == 0); /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ - flecs_table_fini_data(world, table, false, true, false); + flecs_table_fini_data(world, table, false, true, false, true); flecs_table_clear_edges(world, table); if (!is_root) { @@ -40010,10 +40369,12 @@ ecs_table_t* flecs_table_traverse_add( if (node != to || edge->diff) { if (edge->diff) { *diff = *edge->diff; - if (edge->diff->added_flags & EcsIdIsUnion) { - diff->added.array = id_ptr; - diff->added.count = 1; - diff->removed.count = 0; + if (diff->added_flags & EcsIdIsUnion) { + if (diff->added.count == 1) { + diff->added.array = id_ptr; + diff->added.count = 1; + diff->removed.count = 0; + } } } else { diff->added.array = id_ptr; @@ -42017,7 +42378,7 @@ void flecs_json_path( ecs_entity_t e) { ecs_strbuf_appendch(buf, '"'); - ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf); + ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf, true); ecs_strbuf_appendch(buf, '"'); } @@ -42100,15 +42461,15 @@ void flecs_json_id( ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); ecs_strbuf_appendch(buf, '"'); - ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); + ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf, true); ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, '"'); - ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '"'); - ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); + ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); ecs_strbuf_appendch(buf, '"'); } @@ -42125,12 +42486,12 @@ void flecs_json_id_member_fullpath( ecs_strbuf_appendch(buf, '('); ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); - ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); + ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf, true); ecs_strbuf_appendch(buf, ','); - ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); ecs_strbuf_appendch(buf, ')'); } else { - ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); + ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); } } @@ -42140,6 +42501,18 @@ void flecs_json_id_member( ecs_id_t id, bool fullpath) { + ecs_id_t flags = id & ECS_ID_FLAGS_MASK; + + if (flags & ECS_AUTO_OVERRIDE) { + ecs_strbuf_appendlit(buf, "auto_override|"); + id &= ~ECS_AUTO_OVERRIDE; + } + + if (flags & ECS_TOGGLE) { + ecs_strbuf_appendlit(buf, "toggle|"); + id &= ~ECS_TOGGLE; + } + if (fullpath) { flecs_json_id_member_fullpath(buf, world, id); return; @@ -42433,13 +42806,13 @@ void flecs_json_serialize_id_str( ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_first(world, id); ecs_strbuf_appendch(buf, '('); - ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); + ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf, true); ecs_strbuf_appendch(buf, ','); - ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); ecs_strbuf_appendch(buf, ')'); } else { ecs_get_path_w_sep_buf( - world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); + world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); } ecs_strbuf_appendch(buf, '"'); } @@ -42539,17 +42912,49 @@ void flecs_json_serialize_query_plan( const ecs_query_t *q = desc->query; flecs_poly_assert(q, ecs_query_t); - + const ecs_query_t *cq = ecs_query_get_cache_query(q); + flecs_json_memberl(buf, "query_plan"); bool prev_color = ecs_log_enable_colors(true); char *plan = ecs_query_plan(q); + char *cache_plan = NULL; + if (cq) { + flecs_poly_assert(cq, ecs_query_t); + cache_plan = ecs_query_plan(cq); + } + + ecs_strbuf_t plan_buf = ECS_STRBUF_INIT; if (plan) { - flecs_json_string_escape(buf, plan); - ecs_os_free(plan); + ecs_strbuf_appendstr(&plan_buf, plan); + } else { + if (q->term_count) { + ecs_strbuf_append(&plan_buf, " %sOptimized out (trivial query)\n", ECS_GREY); + } + } + + if (cq) { + ecs_strbuf_appendstr(&plan_buf, "\n\n"); + ecs_strbuf_appendstr(&plan_buf, " Cache plan\n"); + ecs_strbuf_appendstr(&plan_buf, " ---\n"); + + if (cache_plan) { + ecs_strbuf_appendstr(&plan_buf, cache_plan); + } else { + ecs_strbuf_append(&plan_buf, " %sOptimized out (trivial query)\n", ECS_GREY); + } + } + + char *plan_str = ecs_strbuf_get(&plan_buf); + if (plan_str) { + flecs_json_string_escape(buf, plan_str); + ecs_os_free(plan_str); } else { flecs_json_null(buf); } + + ecs_os_free(plan); + ecs_os_free(cache_plan); ecs_log_enable_colors(prev_color); } @@ -43291,7 +43696,8 @@ bool flecs_json_serialize_iter_result_ids( ecs_world_t *world = it->world; int16_t f, field_count = flecs_ito(int16_t, it->field_count); - uint16_t field_mask = flecs_ito(uint16_t, (1 << field_count) - 1); + uint32_t field_mask = (uint32_t)((1llu << field_count) - 1); + if (q->static_id_fields == field_mask) { /* All matched ids are static, nothing to serialize */ return false; @@ -43301,7 +43707,7 @@ bool flecs_json_serialize_iter_result_ids( flecs_json_array_push(buf); for (f = 0; f < field_count; f ++) { - ecs_flags16_t field_bit = flecs_ito(uint16_t, 1 << f); + ecs_termset_t field_bit = (ecs_termset_t)(1u << f); if (!(it->set_fields & field_bit)) { /* Don't serialize ids for fields that aren't set */ @@ -43352,7 +43758,7 @@ bool flecs_json_serialize_iter_result_sources( flecs_json_array_push(buf); for (f = 0; f < field_count; f ++) { - ecs_flags16_t field_bit = flecs_ito(uint16_t, 1 << f); + ecs_termset_t field_bit = (ecs_termset_t)(1u << f); if (!(it->set_fields & field_bit)) { /* Don't serialize source for fields that aren't set */ @@ -43423,7 +43829,7 @@ int flecs_json_serialize_iter_result_field_values( ecs_termset_t row_fields = it->query ? it->query->row_fields : 0; for (f = 0; f < field_count; f ++) { - ecs_termset_t field_bit = (ecs_termset_t)flecs_ito(uint64_t, 1 << f); + ecs_termset_t field_bit = (ecs_termset_t)(1u << f); if (!(fields & field_bit)) { ecs_strbuf_list_appendlit(buf, "0"); continue; @@ -43570,21 +43976,20 @@ bool flecs_json_serialize_table_type_info( ecs_table_t *table, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) -{ - ecs_column_t *columns = table->data.columns; - int32_t i, column_count = table->column_count; - +{ flecs_json_memberl(buf, "type_info"); flecs_json_object_push(buf); - if (!column_count) { - flecs_json_object_pop(buf); - return false; - } - - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_id_t id = flecs_column_id(table, i); + int32_t i, type_count = table->type.count; + for (i = 0; i < type_count; i ++) { + const ecs_table_record_t *tr = &table->_->records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_id_t id = table->type.array[i]; + if (!(idr->flags & EcsIdIsSparse) && + (!table->column_map || (table->column_map[i] == -1))) + { + continue; + } if (!desc || !desc->serialize_builtin) { if (flecs_json_is_builtin(id)) { @@ -43592,7 +43997,7 @@ bool flecs_json_serialize_table_type_info( } } - ecs_type_info_t *ti = column->ti; + const ecs_type_info_t *ti = idr->type_info; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_next(buf); @@ -43642,7 +44047,7 @@ bool flecs_json_serialize_table_tags( ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (src_table) { - if (!(idr->flags & EcsIdOnInstantiateInherit)) { + if (idr->flags & EcsIdOnInstantiateDontInherit) { continue; } } @@ -43656,8 +44061,11 @@ bool flecs_json_serialize_table_tags( } flecs_json_next(buf); - flecs_json_path_or_label(buf, world, id, + + ecs_strbuf_appendlit(buf, "\""); + flecs_json_id_member(buf, world, id, desc ? desc->serialize_full_paths : true); + ecs_strbuf_appendlit(buf, "\""); tag_count ++; } @@ -43706,7 +44114,7 @@ bool flecs_json_serialize_table_pairs( ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (src_table) { - if (!(idr->flags & EcsIdOnInstantiateInherit)) { + if (idr->flags & EcsIdOnInstantiateDontInherit) { continue; } } @@ -43779,6 +44187,7 @@ static int flecs_json_serialize_table_components( const ecs_world_t *world, ecs_table_t *table, + const ecs_table_t *src_table, ecs_strbuf_t *buf, ecs_json_value_ser_ctx_t *values_ctx, const ecs_iter_to_json_desc_t *desc, @@ -43799,6 +44208,15 @@ int flecs_json_serialize_table_components( } void *ptr; + const ecs_table_record_t *tr = &table->_->records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + + if (src_table) { + if (idr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } + } + const ecs_type_info_t *ti; int32_t column_index = table->column_map ? table->column_map[i] : -1; if (column_index != -1) { @@ -43806,8 +44224,6 @@ int flecs_json_serialize_table_components( ti = column->ti; ptr = ECS_ELEM(column->data, ti->size, row); } else { - const ecs_table_record_t *tr = &table->_->records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (!(idr->flags & EcsIdIsSparse)) { continue; } @@ -43911,7 +44327,7 @@ bool flecs_json_serialize_table_inherited_type( int32_t component_count = 0; flecs_json_serialize_table_components( - world, base_table, buf, NULL, desc, + world, base_table, table, buf, NULL, desc, ECS_RECORD_TO_ROW(base_record->row), &component_count); if (desc->serialize_type_info) { @@ -44043,7 +44459,7 @@ int flecs_json_serialize_iter_result_table( component_count = 0; /* Each row has the same number of components */ if (flecs_json_serialize_table_components( - world, table, buf, values_ctx, desc, i, &component_count)) + world, table, NULL, buf, values_ctx, desc, i, &component_count)) { result = -1; break; @@ -45539,7 +45955,7 @@ int flecs_expr_ser_primitive( if (!e) { ecs_strbuf_appendlit(str, "#0"); } else { - ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str); + ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str, false); } break; } @@ -49940,6 +50356,7 @@ int flecs_add_constant_to_enum( cptr = ecs_ensure_id(world, e, type); cptr[0] = value; + ecs_modified(world, type, EcsEnum); return 0; } @@ -50431,15 +50848,17 @@ void FlecsMetaImport( ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsType), - .name = "type", .symbol = "EcsType" + .name = "type", .symbol = "EcsType", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsType), - .type.alignment = ECS_ALIGNOF(EcsType) + .type.alignment = ECS_ALIGNOF(EcsType), }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsPrimitive), - .name = "primitive", .symbol = "EcsPrimitive" + .name = "primitive", .symbol = "EcsPrimitive", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsPrimitive), .type.alignment = ECS_ALIGNOF(EcsPrimitive) @@ -50447,13 +50866,15 @@ void FlecsMetaImport( ecs_component(world, { .entity = ecs_entity(world, { .id = EcsConstant, - .name = "constant", .symbol = "EcsConstant" + .name = "constant", .symbol = "EcsConstant", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsEnum), - .name = "enum", .symbol = "EcsEnum" + .name = "enum", .symbol = "EcsEnum", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsEnum), .type.alignment = ECS_ALIGNOF(EcsEnum) @@ -50461,7 +50882,8 @@ void FlecsMetaImport( ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsBitmask), - .name = "bitmask", .symbol = "EcsBitmask" + .name = "bitmask", .symbol = "EcsBitmask", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsBitmask), .type.alignment = ECS_ALIGNOF(EcsBitmask) @@ -50469,7 +50891,8 @@ void FlecsMetaImport( ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsMember), - .name = "member", .symbol = "EcsMember" + .name = "member", .symbol = "EcsMember", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsMember), .type.alignment = ECS_ALIGNOF(EcsMember) @@ -50477,7 +50900,8 @@ void FlecsMetaImport( ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsMemberRanges), - .name = "member_ranges", .symbol = "EcsMemberRanges" + .name = "member_ranges", .symbol = "EcsMemberRanges", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsMemberRanges), .type.alignment = ECS_ALIGNOF(EcsMemberRanges) @@ -50485,7 +50909,8 @@ void FlecsMetaImport( ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsStruct), - .name = "struct", .symbol = "EcsStruct" + .name = "struct", .symbol = "EcsStruct", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsStruct), .type.alignment = ECS_ALIGNOF(EcsStruct) @@ -50493,7 +50918,8 @@ void FlecsMetaImport( ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsArray), - .name = "array", .symbol = "EcsArray" + .name = "array", .symbol = "EcsArray", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsArray), .type.alignment = ECS_ALIGNOF(EcsArray) @@ -50501,7 +50927,8 @@ void FlecsMetaImport( ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsVector), - .name = "vector", .symbol = "EcsVector" + .name = "vector", .symbol = "EcsVector", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsVector), .type.alignment = ECS_ALIGNOF(EcsVector) @@ -50509,7 +50936,8 @@ void FlecsMetaImport( ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsOpaque), - .name = "opaque", .symbol = "EcsOpaque" + .name = "opaque", .symbol = "EcsOpaque", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsOpaque), .type.alignment = ECS_ALIGNOF(EcsOpaque) @@ -54317,8 +54745,10 @@ const char* flecs_parse_multiline_string( "missing '`' to close multiline string"); goto error; } + char *strval = ecs_strbuf_get(&str); if (ecs_meta_set_string(cur, strval) != 0) { + ecs_os_free(strval); goto error; } ecs_os_free(strval); @@ -61781,6 +62211,10 @@ void MonitorStats(ecs_iter_t *it) { cur = 0; count = qit.count; + if (!count) { + cur = -1; + continue; + } } else { cur ++; } @@ -67577,16 +68011,18 @@ ecs_query_cache_t* flecs_query_cache_init( desc.ctx = NULL; desc.binding_ctx = NULL; desc.ctx_free = NULL; - desc.binding_ctx_free = NULL; + desc.binding_ctx_free = NULL; ecs_query_cache_t *result = flecs_bcalloc(&stage->allocators.query_cache); result->entity = entity; impl->cache = result; ecs_observer_desc_t observer_desc = { .query = desc }; + observer_desc.query.flags |= EcsQueryNested; ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; - desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly; + desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly | EcsQueryNested; + ecs_query_t *q = result->query = ecs_query_init(world, &desc); if (!q) { goto error; @@ -68647,7 +69083,8 @@ void flecs_query_mark_fields_dirty( ecs_world_t *world = q->world; int16_t i, field_count = q->field_count; for (i = 0; i < field_count; i ++) { - if (!(write_fields & flecs_ito(uint32_t, 1 << i))) { + ecs_termset_t field_bit = (ecs_termset_t)(1u << i); + if (!(write_fields & field_bit)) { continue; /* If term doesn't write data there's nothing to track */ } @@ -68947,7 +69384,7 @@ bool flecs_query_with( { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_id_record_t *idr = op_ctx->idr; - const ecs_table_record_t *tr; + ecs_table_record_t *tr; ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); if (!table) { @@ -68970,6 +69407,7 @@ bool flecs_query_with( op_ctx->column = flecs_ito(int16_t, tr->index); op_ctx->remaining = flecs_ito(int16_t, tr->count); + op_ctx->it.cur = &tr->hdr; } else { ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, ECS_INTERNAL_ERROR, NULL); @@ -69116,16 +69554,6 @@ bool flecs_query_self_up( } } -static -bool flecs_query_select_any( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - return flecs_query_select_w_id(op, redo, ctx, EcsAny, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); -} - static bool flecs_query_and_any( const ecs_query_op_t *op, @@ -69149,11 +69577,10 @@ bool flecs_query_and_any( remaining = 0; } - if (!redo) { - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - if (match_flags & EcsTermMatchAny && op_ctx->remaining) { - op_ctx->remaining = flecs_ito(int16_t, remaining); - } + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + + if (match_flags & EcsTermMatchAny && op_ctx->remaining) { + op_ctx->remaining = flecs_ito(int16_t, remaining); } int32_t field = op->field_index; @@ -69161,6 +69588,8 @@ bool flecs_query_and_any( ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); } + ctx->it->trs[field] = (ecs_table_record_t*)op_ctx->it.cur; + return result; } @@ -69174,7 +69603,8 @@ bool flecs_query_only_any( if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { return flecs_query_and_any(op, redo, ctx); } else { - return flecs_query_select_any(op, redo, ctx); + return flecs_query_select_w_id(op, redo, ctx, EcsAny, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } } @@ -69568,6 +69998,9 @@ bool flecs_query_each( } if (!redo) { + if (!ecs_table_count(table)) { + return false; + } row = op_ctx->row = range.offset; } else { int32_t end = range.count; @@ -72531,18 +72964,10 @@ void flecs_query_set_iter_this( it->table = table; it->offset = range->offset; it->count = count; -#ifndef FLECS_SANITIZE - it->entities = &ecs_table_entities(table)[it->offset]; - ecs_assert(it->entities != NULL || it->offset == 0, - ECS_INTERNAL_ERROR, NULL); -#else - /* Prevent "applying zero offset to null pointer" sanitizer error. The - * code panics on a bad offset value, but asan doesn't know that. */ it->entities = ecs_table_entities(table); if (it->entities) { it->entities += it->offset; } -#endif } else if (count == 1) { it->count = 1; it->entities = &ctx->vars[0].entity; diff --git a/flecs_ecs_sys/src/flecs.h b/flecs_ecs_sys/src/flecs.h index 7df5091f..1dfa1de1 100644 --- a/flecs_ecs_sys/src/flecs.h +++ b/flecs_ecs_sys/src/flecs.h @@ -35,7 +35,7 @@ /* Flecs version macros */ #define FLECS_VERSION_MAJOR 4 /**< Flecs major version. */ #define FLECS_VERSION_MINOR 0 /**< Flecs minor version. */ -#define FLECS_VERSION_PATCH 1 /**< Flecs patch version. */ +#define FLECS_VERSION_PATCH 3 /**< Flecs patch version. */ /** Flecs version. */ #define FLECS_VERSION FLECS_VERSION_IMPL(\ @@ -481,6 +481,7 @@ extern "C" { #define EcsQueryIsCacheable (1u << 25u) /* All terms of query are cacheable */ #define EcsQueryHasTableThisVar (1u << 26u) /* Does query have $this table var */ #define EcsQueryCacheYieldEmptyTables (1u << 27u) /* Does query cache empty tables */ +#define EcsQueryNested (1u << 28u) /* Query created by a query (for observer, cache) */ //////////////////////////////////////////////////////////////////////////////// //// Term flags (used by ecs_term_t::flags_) @@ -511,7 +512,9 @@ extern "C" { #define EcsObserverIsDisabled (1u << 3u) /* Is observer entity disabled */ #define EcsObserverIsParentDisabled (1u << 4u) /* Is module parent of observer disabled */ #define EcsObserverBypassQuery (1u << 5u) /* Don't evaluate query for multi-component observer*/ -#define EcsObserverYieldOnDelete (1u << 6u) /* Yield matching entities when deleting observer */ +#define EcsObserverYieldOnCreate (1u << 6u) /* Yield matching entities when creating observer */ +#define EcsObserverYieldOnDelete (1u << 7u) /* Yield matching entities when deleting observer */ + //////////////////////////////////////////////////////////////////////////////// //// Table flags (used by ecs_table_t::flags) @@ -699,6 +702,11 @@ extern "C" { /* Produces false positives in queries/src/cache.c */ #pragma GCC diagnostic ignored "-Wstringop-overflow" #pragma GCC diagnostic ignored "-Wrestrict" + +#elif defined(ECS_TARGET_MSVC) +/* recursive on all control paths, function will cause runtime stack overflow + * This warning is incorrectly thrown on enum reflection code. */ +#pragma warning(disable: 4717) #endif /* Allows for enum reflection support on legacy compilers */ @@ -5167,6 +5175,17 @@ FLECS_API ecs_entities_t ecs_get_entities( const ecs_world_t *world); +/** Get flags set on the world. + * This operation returns the internal flags (see api_flags.h) that are + * set on the world. + * + * @param world The world. + * @return Flags set on the world. + */ +FLECS_API +ecs_flags32_t ecs_world_get_flags( + const ecs_world_t *world); + /** @} */ /** @@ -7184,7 +7203,8 @@ void ecs_get_path_w_sep_buf( ecs_entity_t child, const char *sep, const char *prefix, - ecs_strbuf_t *buf); + ecs_strbuf_t *buf, + bool escape); /** Find or create entity from path. * This operation will find or create an entity from a path, and will create any @@ -8154,6 +8174,18 @@ FLECS_API bool ecs_query_is_true( const ecs_query_t *query); +/** Get query used to populate cache. + * This operation returns the query that is used to populate the query cache. + * For queries that are can be entirely cached, the returned query will be + * equivalent to the query passed to ecs_query_get_cache_query(). + * + * @param query The query. + * @return The query used to populate the cache, NULL if query is not cached. + */ +FLECS_API +const ecs_query_t* ecs_query_get_cache_query( + const ecs_query_t *query); + /** @} */ /** @@ -9188,6 +9220,18 @@ int32_t ecs_search_relation( ecs_id_t *id_out, struct ecs_table_record_t **tr_out); +/** Remove all entities in a table. Does not deallocate table memory. + * Retaining table memory can be efficient when planning + * to refill the table with operations like ecs_bulk_init + * + * @param world The world. + * @param table The table to clear. + */ +FLECS_API +void ecs_table_clear_entities( + ecs_world_t* world, + ecs_table_t* table); + /** @} */ /** @@ -9961,7 +10005,7 @@ int ecs_value_move_ctor( ecs_get_path_w_sep(world, 0, child, ".", NULL) #define ecs_get_path_buf(world, child, buf)\ - ecs_get_path_w_sep_buf(world, 0, child, ".", NULL, buf) + ecs_get_path_w_sep_buf(world, 0, child, ".", NULL, buf, false) #define ecs_new_from_path(world, parent, path)\ ecs_new_from_path_w_sep(world, parent, path, ".", NULL) @@ -10300,6 +10344,9 @@ int ecs_value_move_ctor( #ifdef FLECS_NO_SYSTEM #undef FLECS_SYSTEM #endif +#ifdef FLECS_NO_ALERTS +#undef FLECS_ALERTS +#endif #ifdef FLECS_NO_PIPELINE #undef FLECS_PIPELINE #endif @@ -14632,6 +14679,11 @@ extern "C" { FLECS_API extern const ecs_entity_t ecs_id(EcsDocDescription); /**< Component id for EcsDocDescription. */ +/** Tag for adding a UUID to entities. + * Added to an entity as (EcsDocDescription, EcsUuid) by ecs_doc_set_uuid(). + */ +FLECS_API extern const ecs_entity_t EcsDocUuid; + /** Tag for adding brief descriptions to entities. * Added to an entity as (EcsDocDescription, EcsBrief) by ecs_doc_set_brief(). */ @@ -14664,6 +14716,23 @@ typedef struct EcsDocDescription { char *value; } EcsDocDescription; +/** Add UUID to entity. + * Associate entity with an (external) UUID. + * + * @param world The world. + * @param entity The entity to which to add the UUID. + * @param uuid The UUID to add. + * + * @see ecs_doc_get_uuid() + * @see flecs::doc::set_uuid() + * @see flecs::entity_builder::set_doc_uuid() + */ +FLECS_API +void ecs_doc_set_uuid( + ecs_world_t *world, + ecs_entity_t entity, + const char *uuid); + /** Add human-readable name to entity. * Contrary to entity names, human readable names do not have to be unique and * can contain special characters used in the query language like '*'. @@ -14747,6 +14816,20 @@ void ecs_doc_set_color( ecs_entity_t entity, const char *color); +/** Get UUID from entity. + * @param world The world. + * @param entity The entity from which to get the UUID. + * @return The UUID. + * + * @see ecs_doc_set_uuid() + * @see flecs::doc::get_uuid() + * @see flecs::entity_view::get_doc_uuid() + */ +FLECS_API +const char* ecs_doc_get_uuid( + const ecs_world_t *world, + ecs_entity_t entity); + /** Get human readable name from entity. * If entity does not have an explicit human readable name, this operation will * return the entity name. @@ -17221,11 +17304,11 @@ struct string_view : string { #if defined(__clang__) && __clang_major__ >= 16 // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 -#define enum_cast(T, v) __builtin_bit_cast(T, v) +#define flecs_enum_cast(T, v) __builtin_bit_cast(T, v) #elif defined(__GNUC__) && __GNUC__ > 10 -#define enum_cast(T, v) __builtin_bit_cast(T, v) +#define flecs_enum_cast(T, v) __builtin_bit_cast(T, v) #else -#define enum_cast(T, v) static_cast(v) +#define flecs_enum_cast(T, v) static_cast(v) #endif namespace flecs { @@ -17234,7 +17317,7 @@ namespace flecs { namespace _ { template Value> struct to_constant { - static constexpr E value = enum_cast(E, Value); + static constexpr E value = flecs_enum_cast(E, Value); }; template Value> @@ -17347,7 +17430,7 @@ constexpr bool enum_constant_is_valid() { /* Without this wrapper __builtin_bit_cast doesn't work */ template C> constexpr bool enum_constant_is_valid_wrap() { - return enum_constant_is_valid(); + return enum_constant_is_valid(); } template @@ -17514,7 +17597,7 @@ struct enum_type { static U handle_constant(U last_value, flecs::world_t *world) { // Constant is valid, so fill reflection data. auto v = Value; - const char *name = enum_constant_to_name(); + const char *name = enum_constant_to_name(); ++enum_type::data.max; // Increment cursor as we build constants array. @@ -18443,6 +18526,9 @@ namespace doc { /** flecs.doc.Description component */ using Description = EcsDocDescription; +/** flecs.doc.Uuid component */ +static const flecs::entity_t Uuid = EcsDocUuid; + /** flecs.doc.Brief component */ static const flecs::entity_t Brief = EcsDocBrief; @@ -22789,6 +22875,14 @@ struct iter { iter_->callback(iter_); } + /** Iterate targets for pair field. + * + * @param index The field index. + * @param func Callback invoked for each target + */ + template + void targets(int8_t index, const Func& func); + /** Free iterator resources. * This operation only needs to be called when the iterator is not iterated * until completion (e.g. the last call to next() did not return false). @@ -23197,11 +23291,11 @@ struct entity_view : public id { */ template::value> = 0> const First* get(Second second) const { - auto comp_id = _::type::id(world_); + auto first = _::type::id(world_); ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, "operation invalid for empty type"); return static_cast( - ecs_get_id(world_, id_, ecs_pair(comp_id, second))); + ecs_get_id(world_, id_, ecs_pair(first, second))); } /** Get a pair. @@ -23294,6 +23388,10 @@ struct entity_view : public id { template const Second* get_second(flecs::entity_t first) const { auto second = _::type::id(world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, "operation invalid for empty type"); return static_cast( @@ -23363,11 +23461,11 @@ struct entity_view : public id { */ template::value> = 0> First* get_mut(Second second) const { - auto comp_id = _::type::id(world_); + auto first = _::type::id(world_); ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, "operation invalid for empty type"); return static_cast( - ecs_get_mut_id(world_, id_, ecs_pair(comp_id, second))); + ecs_get_mut_id(world_, id_, ecs_pair(first, second))); } /** Get a mutable pair. @@ -23415,6 +23513,10 @@ struct entity_view : public id { template Second* get_mut_second(flecs::entity_t first) const { auto second = _::type::id(world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, "operation invalid for empty type"); return static_cast( @@ -23856,6 +23958,19 @@ const char* doc_color() const { return ecs_doc_get_color(world_, id_); } +/** Get UUID. + * + * @see ecs_doc_get_uuid() + * @see flecs::doc::get_uuid() + * @see flecs::entity_builder::set_doc_uuid() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_uuid() const { + return ecs_doc_get_uuid(world_, id_); +} + # endif # ifdef FLECS_ALERTS /** @@ -24807,6 +24922,10 @@ struct entity_builder : entity_view { template const Self& set_second(entity_t first, const Second& value) const { auto second = _::type::id(this->world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); flecs::set(this->world_, this->id_, value, ecs_pair(first, second)); return to_base(); @@ -24823,6 +24942,10 @@ struct entity_builder : entity_view { template const Self& set_second(entity_t first, Second&& value) const { auto second = _::type::id(this->world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); flecs::set(this->world_, this->id_, FLECS_FWD(value), ecs_pair(first, second)); return to_base(); @@ -24892,16 +25015,22 @@ struct entity_builder : entity_view { template const Self& emplace_first(flecs::entity_t second, Args&&... args) const { + auto first = _::type::id(this->world_); flecs::emplace(this->world_, this->id_, - ecs_pair(_::type::id(this->world_), second), + ecs_pair(first, second), FLECS_FWD(args)...); return to_base(); } template const Self& emplace_second(flecs::entity_t first, Args&&... args) const { + auto second = _::type::id(this->world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); flecs::emplace(this->world_, this->id_, - ecs_pair(first, _::type::id(this->world_)), + ecs_pair(first, second), FLECS_FWD(args)...); return to_base(); } @@ -25050,8 +25179,23 @@ const Self& set_doc_link(const char *link) const { * @memberof flecs::entity_builder * @ingroup cpp_addons_doc */ -const Self& set_doc_color(const char *link) const { - ecs_doc_set_color(world_, id_, link); +const Self& set_doc_color(const char *color) const { + ecs_doc_set_color(world_, id_, color); + return to_base(); +} + +/** Set doc UUID. + * This adds `(flecs.doc.Description, flecs.doc.Uuid)` to the entity. + * + * @see ecs_doc_set_uuid() + * @see flecs::doc::set_uuid() + * @see flecs::entity_view::doc_uuid() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc + */ +const Self& set_doc_uuid(const char *uuid) const { + ecs_doc_set_uuid(world_, id_, uuid); return to_base(); } @@ -25434,11 +25578,11 @@ struct entity : entity_builder */ template First& ensure(entity_t second) const { - auto comp_id = _::type::id(world_); + auto first = _::type::id(world_); ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, "operation invalid for empty type"); return *static_cast( - ecs_ensure_id(world_, id_, ecs_pair(comp_id, second))); + ecs_ensure_id(world_, id_, ecs_pair(first, second))); } /** Get mutable pointer for a pair (untyped). @@ -25462,6 +25606,10 @@ struct entity : entity_builder template Second& ensure_second(entity_t first) const { auto second = _::type::id(world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, "operation invalid for empty type"); return *static_cast( @@ -25487,9 +25635,13 @@ struct entity : entity_builder * @tparam First The first part of the pair. * @tparam Second the second part of the pair. */ - template + template >> void modified() const { - this->modified(_::type::id(world_)); + auto first = _::type::id(world_); + auto second = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + this->modified(first, second); } /** Signal that the first part of a pair was modified. @@ -25557,20 +25709,23 @@ struct entity : entity_builder typename A = actual_type_t

> ref get_ref() const { return ref(world_, id_, - ecs_pair(_::type::id(world_), - _::type::id(world_))); + ecs_pair(_::type::id(world_), _::type::id(world_))); } template ref get_ref(flecs::entity_t second) const { - return ref(world_, id_, - ecs_pair(_::type::id(world_), second)); + auto first = _::type::id(world_); + return ref(world_, id_, ecs_pair(first, second)); } template ref get_ref_second(flecs::entity_t first) const { - return ref(world_, id_, - ecs_pair(first, _::type::id(world_))); + auto second = _::type::id(world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + return ref(world_, id_, ecs_pair(first, second)); } /** Clear an entity. @@ -30342,6 +30497,12 @@ struct observer_builder_i : query_builder_i { return *this; } + /** Set observer flags */ + Base& observer_flags(ecs_flags32_t flags) { + desc_->flags_ |= flags; + return *this; + } + /** Set observer context */ Base& ctx(void *ptr) { desc_->ctx = ptr; @@ -30657,7 +30818,7 @@ inline flecs::entity world::module(const char *name) const { if (prev_parent != parent) { // Module was reparented, cleanup old parent(s) flecs::entity cur = prev_parent, next; - do { + while (cur) { next = cur.parent(); ecs_iter_t it = ecs_each_id(world_, ecs_pair(EcsChildOf, cur)); @@ -30666,7 +30827,7 @@ inline flecs::entity world::module(const char *name) const { } cur = next; - } while (cur); + } } } @@ -31339,6 +31500,18 @@ inline void timer_init(flecs::world& world) { namespace flecs { namespace doc { +/** Get UUID for an entity. + * + * @see ecs_doc_get_uuid() + * @see flecs::doc::set_uuid() + * @see flecs::entity_view::doc_uuid() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_uuid(const flecs::entity_view& e) { + return ecs_doc_get_uuid(e.world(), e); +} + /** Get human readable name for an entity. * * @see ecs_doc_get_name() @@ -31399,6 +31572,18 @@ inline const char* get_color(const flecs::entity_view& e) { return ecs_doc_get_color(e.world(), e); } +/** Set UUID for an entity. + * + * @see ecs_doc_set_uuid() + * @see flecs::doc::get_uuid() + * @see flecs::entity_builder::set_doc_uuid() + * + * @ingroup cpp_addons_doc + */ +inline void set_uuid(flecs::entity& e, const char *uuid) { + ecs_doc_set_uuid(e.world(), e, uuid); +} + /** Set human readable name for an entity. * * @see ecs_doc_set_name() @@ -32472,6 +32657,24 @@ inline flecs::entity iter::get_var(const char *name) const { return flecs::entity(iter_->world, ecs_iter_get_var(iter_, var_id)); } +template +void iter::targets(int8_t index, const Func& func) { + ecs_assert(iter_->table != nullptr, ECS_INVALID_OPERATION, NULL); + ecs_assert(index < iter_->field_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_field_is_set(iter_, index), ECS_INVALID_PARAMETER, NULL); + const ecs_type_t *table_type = ecs_table_get_type(iter_->table); + const ecs_table_record_t *tr = iter_->trs[index]; + int32_t i = tr->index, end = i + tr->count; + for (; i < end; i ++) { + ecs_id_t id = table_type->array[i]; + ecs_assert(ECS_IS_PAIR(id), ECS_INVALID_PARAMETER, + "field does not match a pair"); + flecs::entity tgt(iter_->world, + ecs_pair_second(iter_->real_world, id)); + func(tgt); + } +} + } // namespace flecs /**