Skip to content

Commit

Permalink
Implement Dynamic Systems and Components
Browse files Browse the repository at this point in the history
  • Loading branch information
zicklag committed Nov 4, 2020
1 parent 0798295 commit 07a44c0
Show file tree
Hide file tree
Showing 21 changed files with 1,402 additions and 285 deletions.
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ render = ["bevy_pbr", "bevy_render", "bevy_sprite", "bevy_text", "bevy_ui"]
png = ["bevy_render/png"]
hdr = ["bevy_render/hdr"]

# Enable the dynamic systems and components API ( useful for developing scripting solutions )
dynamic-api = ["bevy_ecs/dynamic-api", "bevy_scene/dynamic-api"]

# Audio format support (MP3 is enabled by default)
mp3 = ["bevy_audio/mp3"]
flac = ["bevy_audio/flac"]
Expand Down Expand Up @@ -90,6 +93,8 @@ serde = { version = "1", features = ["derive"] }
log = "0.4"
ron = "0.6"
anyhow = "1.0"
lazy_static = "1.4.0"


# bevy (Android)
[target.'cfg(target_os = "android")'.dependencies]
Expand Down Expand Up @@ -216,6 +221,16 @@ path = "examples/ecs/parallel_query.rs"
name = "hierarchy"
path = "examples/ecs/hierarchy.rs"

[[example]]
name = "dynamic_systems"
path = "examples/ecs/dynamic_systems.rs"
required-features = ["dynamic-api"]

[[example]]
name = "dynamic_components"
path = "examples/ecs/dynamic_components.rs"
required-features = ["dynamic-api"]

[[example]]
name = "breakout"
path = "examples/game/breakout.rs"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ categories = ["game-engines", "data-structures"]

[features]
profiler = []
dynamic-api = ["bevy_hecs/dynamic-api"]

[dependencies]
bevy_hecs = { path = "hecs", features = ["macros", "serialize"], version = "0.3.0" }
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_ecs/hecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ std = []
# Enables derive(Bundle)
macros = ["bevy_hecs_macros", "lazy_static"]
serialize = ["serde"]
# Enables the dynamic components and systems APIs
dynamic-api = ["std"]

[dependencies]
bevy_hecs_macros = { path = "macros", version = "0.3.0", optional = true }
Expand Down
16 changes: 13 additions & 3 deletions crates/bevy_ecs/hecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,12 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream {
let query_fn = &query_fns[0..query_count];
let query_fn_mut = &query_fn_muts[0..query_count];
tokens.extend(TokenStream::from(quote! {
impl<#(#lifetime,)* #(#query: HecsQuery,)*> QueryTuple for (#(Query<#lifetime, #query>,)*) {
impl<#(#lifetime,)* #(#query: HecsQuery,)*> QueryTuple for (#(Query<#lifetime, #query>,)*)
where
#(
#query::Fetch: for<'a> Fetch<'a, State = ()>
),*
{
unsafe fn new(world: &World, component_access: &TypeAccess<ArchetypeComponent>) -> Self {
(
#(
Expand All @@ -200,12 +205,17 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream {

fn get_accesses() -> Vec<QueryAccess> {
vec![
#(<#query::Fetch as Fetch>::access(),)*
#(<#query::Fetch as Fetch>::access(&()),)*
]
}
}

impl<#(#lifetime,)* #(#query: HecsQuery,)*> QuerySet<(#(Query<#lifetime, #query>,)*)> {
impl<#(#lifetime,)* #(#query: HecsQuery,)*> QuerySet<(#(Query<#lifetime, #query>,)*)>
where
#(
#query::Fetch: for<'a> Fetch<'a, State = ()>
),*
{
#(#query_fn)*
#(#query_fn_mut)*
}
Expand Down
69 changes: 36 additions & 33 deletions crates/bevy_ecs/hecs/src/access.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::{any::TypeId, hash::Hash};
use std::{boxed::Box, vec::Vec};

use crate::{Archetype, World};
use crate::{Archetype, ComponentId, World};
use bevy_utils::HashSet;

#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
Expand All @@ -14,52 +14,53 @@ pub enum Access {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ArchetypeComponent {
pub archetype_index: u32,
pub component: TypeId,
pub component: ComponentId,
}

impl ArchetypeComponent {
#[inline]
pub fn new<T: 'static>(archetype_index: u32) -> Self {
ArchetypeComponent {
archetype_index,
component: TypeId::of::<T>(),
component: TypeId::of::<T>().into(),
}
}

#[inline]
pub fn new_ty(archetype_index: u32, component: TypeId) -> Self {
pub fn new_component(archetype_index: u32, component: ComponentId) -> Self {
ArchetypeComponent {
archetype_index,
component,
}
}
}

#[derive(Clone, Debug)]
pub enum QueryAccess {
None,
Read(TypeId, &'static str),
Write(TypeId, &'static str),
Read(ComponentId, &'static str),
Write(ComponentId, &'static str),
Optional(Box<QueryAccess>),
With(TypeId, Box<QueryAccess>),
Without(TypeId, Box<QueryAccess>),
With(ComponentId, Box<QueryAccess>),
Without(ComponentId, Box<QueryAccess>),
Union(Vec<QueryAccess>),
}

impl QueryAccess {
pub fn read<T: 'static>() -> QueryAccess {
QueryAccess::Read(TypeId::of::<T>(), std::any::type_name::<T>())
QueryAccess::Read(TypeId::of::<T>().into(), std::any::type_name::<T>())
}

pub fn write<T: 'static>() -> QueryAccess {
QueryAccess::Write(TypeId::of::<T>(), std::any::type_name::<T>())
QueryAccess::Write(TypeId::of::<T>().into(), std::any::type_name::<T>())
}

pub fn with<T: 'static>(access: QueryAccess) -> QueryAccess {
QueryAccess::With(TypeId::of::<T>(), Box::new(access))
QueryAccess::With(TypeId::of::<T>().into(), Box::new(access))
}

pub fn without<T: 'static>(access: QueryAccess) -> QueryAccess {
QueryAccess::Without(TypeId::of::<T>(), Box::new(access))
QueryAccess::Without(TypeId::of::<T>().into(), Box::new(access))
}

pub fn optional(access: QueryAccess) -> QueryAccess {
Expand All @@ -82,29 +83,29 @@ impl QueryAccess {
}
}

pub fn get_type_name(&self, type_id: TypeId) -> Option<&'static str> {
pub fn get_type_name(&self, component_id: ComponentId) -> Option<&'static str> {
match self {
QueryAccess::None => None,
QueryAccess::Read(current_type_id, name) => {
if type_id == *current_type_id {
QueryAccess::Read(current_component_id, name) => {
if component_id == *current_component_id {
Some(*name)
} else {
None
}
}
QueryAccess::Write(current_type_id, name) => {
if type_id == *current_type_id {
QueryAccess::Write(current_component_id, name) => {
if component_id == *current_component_id {
Some(*name)
} else {
None
}
}
QueryAccess::Optional(query_access) => query_access.get_type_name(type_id),
QueryAccess::With(_, query_access) => query_access.get_type_name(type_id),
QueryAccess::Without(_, query_access) => query_access.get_type_name(type_id),
QueryAccess::Optional(query_access) => query_access.get_type_name(component_id),
QueryAccess::With(_, query_access) => query_access.get_type_name(component_id),
QueryAccess::Without(_, query_access) => query_access.get_type_name(component_id),
QueryAccess::Union(query_accesses) => {
for query_access in query_accesses.iter() {
if let Some(name) = query_access.get_type_name(type_id) {
if let Some(name) = query_access.get_type_name(component_id) {
return Some(name);
}
}
Expand All @@ -125,19 +126,21 @@ impl QueryAccess {
match self {
QueryAccess::None => Some(Access::None),
QueryAccess::Read(ty, _) => {
if archetype.has_type(*ty) {
if archetype.has_component(*ty) {
if let Some(type_access) = type_access {
type_access.add_read(ArchetypeComponent::new_ty(archetype_index, *ty));
type_access
.add_read(ArchetypeComponent::new_component(archetype_index, *ty));
}
Some(Access::Read)
} else {
None
}
}
QueryAccess::Write(ty, _) => {
if archetype.has_type(*ty) {
if archetype.has_component(*ty) {
if let Some(type_access) = type_access {
type_access.add_write(ArchetypeComponent::new_ty(archetype_index, *ty));
type_access
.add_write(ArchetypeComponent::new_component(archetype_index, *ty));
}
Some(Access::Write)
} else {
Expand All @@ -157,14 +160,14 @@ impl QueryAccess {
}
}
QueryAccess::With(ty, query_access) => {
if archetype.has_type(*ty) {
if archetype.has_component(*ty) {
query_access.get_access(archetype, archetype_index, type_access)
} else {
None
}
}
QueryAccess::Without(ty, query_access) => {
if !archetype.has_type(*ty) {
if !archetype.has_component(*ty) {
query_access.get_access(archetype, archetype_index, type_access)
} else {
None
Expand Down Expand Up @@ -308,7 +311,7 @@ mod tests {
let e3_c = ArchetypeComponent::new::<C>(e3_archetype);

let mut a_type_access = TypeAccess::default();
<(&A,) as Query>::Fetch::access()
<(&A,) as Query>::Fetch::access(&())
.get_world_archetype_access(&world, Some(&mut a_type_access));

assert_eq!(
Expand All @@ -317,7 +320,7 @@ mod tests {
);

let mut a_b_type_access = TypeAccess::default();
<(&A, &B) as Query>::Fetch::access()
<(&A, &B) as Query>::Fetch::access(&())
.get_world_archetype_access(&world, Some(&mut a_b_type_access));

assert_eq!(
Expand All @@ -326,7 +329,7 @@ mod tests {
);

let mut a_bmut_type_access = TypeAccess::default();
<(&A, &mut B) as Query>::Fetch::access()
<(&A, &mut B) as Query>::Fetch::access(&())
.get_world_archetype_access(&world, Some(&mut a_bmut_type_access));

assert_eq!(
Expand All @@ -335,7 +338,7 @@ mod tests {
);

let mut a_option_bmut_type_access = TypeAccess::default();
<(Entity, &A, Option<&mut B>) as Query>::Fetch::access()
<(Entity, &A, Option<&mut B>) as Query>::Fetch::access(&())
.get_world_archetype_access(&world, Some(&mut a_option_bmut_type_access));

assert_eq!(
Expand All @@ -344,7 +347,7 @@ mod tests {
);

let mut a_with_b_type_access = TypeAccess::default();
<With<B, &A> as Query>::Fetch::access()
<With<B, &A> as Query>::Fetch::access(&())
.get_world_archetype_access(&world, Some(&mut a_with_b_type_access));

assert_eq!(
Expand All @@ -353,7 +356,7 @@ mod tests {
);

let mut a_with_b_option_c_type_access = TypeAccess::default();
<With<B, (&A, Option<&mut C>)> as Query>::Fetch::access()
<With<B, (&A, Option<&mut C>)> as Query>::Fetch::access(&())
.get_world_archetype_access(&world, Some(&mut a_with_b_option_c_type_access));

assert_eq!(
Expand Down
44 changes: 30 additions & 14 deletions crates/bevy_ecs/hecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use bevy_utils::AHasher;
use core::{
any::TypeId,
cell::UnsafeCell,
hash::{BuildHasherDefault, Hasher},
hash::{BuildHasherDefault, Hash, Hasher},
mem,
ptr::{self, NonNull},
};
Expand Down Expand Up @@ -243,7 +243,8 @@ impl Archetype {
size: usize,
index: usize,
) -> Option<NonNull<u8>> {
debug_assert!(index < self.len);
// TODO(zicklag): I'm pretty sure that it is valid for the index to be zero
debug_assert!(index < self.len || index == 0);
Some(NonNull::new_unchecked(
(*self.data.get())
.as_ptr()
Expand Down Expand Up @@ -500,11 +501,14 @@ impl TypeState {
}

/// Metadata required to store a component
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct TypeInfo {
id: ComponentId,
layout: Layout,
drop: unsafe fn(*mut u8),
/// The ID unique to the component type
pub(crate) id: ComponentId,
/// The memory layout of the component
pub(crate) layout: Layout,
/// The drop function for the component
pub(crate) drop: unsafe fn(*mut u8),
}

impl TypeInfo {
Expand All @@ -521,6 +525,16 @@ impl TypeInfo {
}
}

/// Get the [`TypeInfo`] for an external type with the given layout and drop function
#[cfg(feature = "dynamic-api")]
pub fn of_external(external_id: u64, layout: Layout, drop: unsafe fn(*mut u8)) -> Self {
TypeInfo {
id: ComponentId::ExternalId(external_id),
layout,
drop,
}
}

#[allow(missing_docs)]
#[inline]
pub fn id(&self) -> ComponentId {
Expand All @@ -538,6 +552,16 @@ impl TypeInfo {
}
}

#[allow(clippy::derive_hash_xor_eq)]
impl Hash for TypeInfo {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
state.write_usize(self.layout.size());
state.write_usize(self.layout.align());
self.drop.hash(state);
}
}

impl PartialOrd for TypeInfo {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
Expand All @@ -555,14 +579,6 @@ impl Ord for TypeInfo {
}
}

impl PartialEq for TypeInfo {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}

impl Eq for TypeInfo {}

fn align(x: usize, alignment: usize) -> usize {
debug_assert!(alignment.is_power_of_two());
(x + alignment - 1) & (!alignment + 1)
Expand Down
Loading

0 comments on commit 07a44c0

Please sign in to comment.