diff --git a/.travis.yml b/.travis.yml index 96cb9b9ed..19e176ab9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: rust + rust: - nightly -- beta +- 1.28.0 - stable cache: @@ -17,12 +18,19 @@ before_script: - export PATH="$PATH:$HOME/.cargo/bin" script: -- cargo build --verbose --features "common serde rudy" -- cargo test --verbose --features "common serde rudy" - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo build --all-features --verbose; - cargo test --all-features --verbose; + cargo test --all-features --verbose --no-run; cargo bench --verbose --no-run --all-features; + elif [ "$TRAVIS_RUST_VERSION" == "1.28.0" ]; then + cargo check --tests --no-default-features; + cargo check --tests --no-default-features --features "parallel"; + cargo check --tests --no-default-features --features "serde"; + cargo check --tests --no-default-features --features "serde,parallel"; + else + cargo build --verbose --no-default-features; + cargo test --verbose --no-default-features; + cargo test --verbose --features "parallel,serde"; fi - cargo build --manifest-path ./specs-derive/Cargo.toml --verbose - cargo test --manifest-path ./specs-derive/Cargo.toml --verbose diff --git a/Cargo.toml b/Cargo.toml index cb2b1dad8..c76383244 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,13 @@ name = "specs" version = "0.14.3" description = """ -Specs is an Entity-Component System library written in Rust. +Specs is an Entity-Component-System library written in Rust. """ documentation = "https://docs.rs/specs/" repository = "https://github.com/slide-rs/specs" homepage = "https://slide-rs.github.io/" readme = "README.md" -keywords = ["gamedev", "ecs"] +keywords = ["gamedev", "ecs", "entity", "component"] categories = ["concurrency"] license = "MIT/Apache-2.0" authors = ["slide-rs hackers"] @@ -34,20 +34,15 @@ tuple_utils = "0.3" rayon = { version = "1.0.0", optional = true } nonzero_signed = "1.0.1" -futures = { version = "0.1", optional = true } serde = { version = "1.0", optional = true, features = ["serde_derive"] } -# enable rudy via --features rudy -rudy = { version = "0.1", optional = true } - [features] default = ["parallel"] parallel = ["rayon", "shred/parallel", "hibitset/parallel"] -common = ["futures"] nightly = ["shred/nightly"] [package.metadata.docs.rs] -features = ["common", "serde"] +features = ["parallel", "nightly"] [dev-dependencies] cgmath = { version = "0.16" } @@ -78,10 +73,6 @@ name = "track" [[example]] name = "ordered_track" -[[example]] -name = "common" -required-features = ["common"] - [[example]] name = "saveload" required-features = ["serde"] diff --git a/book/src/05_storages.md b/book/src/05_storages.md index 40a88b93c..1a23843b0 100644 --- a/book/src/05_storages.md +++ b/book/src/05_storages.md @@ -81,14 +81,3 @@ just leaves uninitialized gaps where we don't have any component. Therefore it would be a waste of memory to use this storage for rare components, but it's best suited for commonly used components (like transform values). - - -## `RudyStorage` (Experimental) - -There is an experimental `RudyStorage`, which can be enabled with the `rudy` feature -flag. It uses [the `rudy` crate][ru], the Rust implementation of [Judy Arrays][ja]. - -[ja]: http://judy.sourceforge.net -[ru]: https://crates.io/crates/rudy - -Its benefits are not clear yet. diff --git a/examples/common.rs b/examples/common.rs deleted file mode 100644 index c89d50ffa..000000000 --- a/examples/common.rs +++ /dev/null @@ -1,81 +0,0 @@ -extern crate futures; -extern crate specs; - -use std::error::Error; -use std::fmt::{Display, Formatter, Result as FmtResult}; - -use futures::future::Lazy; -use futures::{Future, Poll}; -use specs::common::{BoxedFuture, Errors, Merge}; -use specs::error::BoxedErr; -use specs::prelude::*; - -struct MyFloat(f32); - -struct MyFuture(BoxedFuture); - -impl Future for MyFuture { - type Item = MyFloat; - type Error = BoxedErr; - - fn poll(&mut self) -> Poll { - self.0.poll() - } -} - -impl Component for MyFloat { - type Storage = VecStorage; -} - -impl Component for MyFuture { - type Storage = DenseVecStorage; -} - -fn main() { - let mut world = World::new(); - - world.register::(); - world.register::(); - - world.add_resource(Errors::new()); - - world.create_entity().with(future_sqrt(25.0)).build(); - - let mut dispatcher = DispatcherBuilder::new() - .with(Merge::::new(), "merge_my_float", &[]) - .build(); - - dispatcher.dispatch(&world.res); - - world.write_resource::().print_and_exit(); -} - -#[derive(Debug)] -enum MyErr { - _What, - _Ever, -} - -impl Display for MyErr { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "{}", self.description()) - } -} - -impl Error for MyErr { - fn description(&self) -> &str { - match *self { - MyErr::_What => "What", - MyErr::_Ever => "Ever", - } - } -} - -fn future_sqrt(num: f32) -> MyFuture { - use futures::lazy; - - let lazy: Lazy<_, Result<_, MyErr>> = lazy(move || Ok(num.sqrt())); - let future = lazy.map(MyFloat).map_err(BoxedErr::new); - - MyFuture(Box::new(future)) -} diff --git a/src/common.rs b/src/common.rs deleted file mode 100644 index 9882d08be..000000000 --- a/src/common.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! Common functionality between crates using specs. -//! -//! At the moment, this module provides two types: -//! -//! * `Errors`: A resource you can use to store errors that occurred outside of -//! the ECS but were catched inside, therefore should be handled by the user -//! -//! * `Merge`: A system generic over `T` which automatically merges `Ready` futures into -//! the component storage for `T`. -//! -//! To make use of these features, you need to ask for the `common` feature -//! like this: -//! -//! ```toml -//! [dependencies.specs] -//! # version = "..." -//! features = ["common"] -//! ``` - -use std::convert::AsRef; -use std::error::Error; -use std::io::Write; -use std::marker::PhantomData; - -use crossbeam::queue::MsQueue; -use futures::executor::{spawn, Notify, Spawn}; -use futures::{Async, Future}; - -use error::BoxedErr; -use join::Join; -use shred::{Read, RunningTime, System}; -use storage::WriteStorage; -use world::{Component, Entities, Entity}; - -/// A boxed, thread-safe future with `T` as item and `BoxedErr` as error type. -pub type BoxedFuture = Box + Send + Sync + 'static>; - -/// A draining iterator for `Errors`. -/// This is the return value of `Errors::drain`. -#[derive(Debug)] -pub struct DrainErrors<'a> { - queue: &'a mut MsQueue, -} - -impl<'a> Iterator for DrainErrors<'a> { - type Item = BoxedErr; - - fn next(&mut self) -> Option { - self.queue.try_pop() - } -} - -/// A resource you can use to store errors that occurred outside of -/// the ECS but were catched inside, therefore should be handled by the user. -#[derive(Debug)] -pub struct Errors { - /// The collection of errors. - errors: MsQueue, -} - -impl Default for Errors { - fn default() -> Self { - Errors::new() - } -} - -impl Errors { - /// Creates a new instance of `Errors`. - pub fn new() -> Self { - Errors { - errors: MsQueue::new(), - } - } - - /// Add an error to the error collection. - pub fn add(&self, error: BoxedErr) { - self.errors.push(error); - } - - /// A convenience method which allows nicer error handling. - /// - /// ## Examples - /// - /// ``` - /// # use specs::common::Errors; - /// # let errors = Errors::new(); - /// # use std::io::{Error, ErrorKind}; - /// # fn do_something() -> Result { Err(Error::new(ErrorKind::Other, "Other")) } - /// errors.execute::(|| { - /// let y = do_something()?; - /// println!("{}", y + 5); - /// - /// Ok(()) - /// }); - /// ``` - /// - /// So the closure you pass to this method is essentially an environment where you can - /// use `?` for error handling. - pub fn execute(&self, f: F) - where - E: Error + Send + Sync + 'static, - F: FnOnce() -> Result<(), E>, - { - if let Err(e) = f() { - self.add(BoxedErr::new(e)); - } - } - - /// Checks if the queue contains at least one error. - pub fn has_error(&self) -> bool { - !self.errors.is_empty() - } - - /// Removes the first error from the queue. - pub fn pop_err(&mut self) -> Option { - self.errors.try_pop() - } - - /// Returns a draining iterator, removing elements - /// on each call to `Iterator::next`. - pub fn drain(&mut self) -> DrainErrors { - DrainErrors { - queue: &mut self.errors, - } - } - - /// Collect all errors into a `Vec`. - pub fn collect(&mut self) -> Vec { - self.drain().collect() - } - - /// Prints all errors and exits in case there's been an error. Useful for debugging. - pub fn print_and_exit(&mut self) { - use std::io::stderr; - use std::process::exit; - - if self.errors.is_empty() { - return; - } - - let mut errors = self.collect(); - - let stderr = stderr(); - let mut stderr = stderr.lock(); - - writeln!( - &mut stderr, - "Exiting program because of {} errors...", - errors.len() - ).unwrap(); - - for (ind, error) in errors.drain(..).enumerate() { - let error = error.as_ref(); - - writeln!(&mut stderr, "{}: {}", ind, error).unwrap(); - } - - exit(1); - } -} - -/// A system which merges `Ready` futures into the persistent storage. -/// Please note that your `World` has to contain a component storage -/// for `F` and `F::Item`. -/// -/// In case of an error, it will be added to the `Errors` resource. -#[derive(Derivative)] -#[derivative(Default(bound = ""))] -pub struct Merge { - #[derivative(Default(value = "PhantomData"))] - future_type: PhantomData, - spawns: Vec<(Entity, Spawn)>, -} - -impl Merge { - /// Creates a new merge system. - pub fn new() -> Self { - Default::default() - } -} - -impl<'a, T, F> System<'a> for Merge -where - T: Component + Send + Sync + 'static, - F: Future + Component + Send + Sync, -{ - type SystemData = ( - Entities<'a>, - Read<'a, Errors>, - WriteStorage<'a, F>, - WriteStorage<'a, T>, - ); - - fn run(&mut self, (entities, errors, mut futures, mut pers): Self::SystemData) { - for (e, future) in (&entities, futures.drain()).join() { - self.spawns.push((e, spawn(future))); - } - - retain_mut(&mut self.spawns, |spawn| { - match spawn.1.poll_future_notify(NOTIFY_IGNORE, 0) { - Ok(Async::NotReady) => true, - Ok(Async::Ready(value)) => { - // It's not possible for this to fail, as we've - // already proven the entity is alive by grabbing a - // component from it. - pers.insert(spawn.0, value).unwrap(); - false - } - Err(err) => { - errors.add(err); - false - } - } - }); - } - - fn running_time(&self) -> RunningTime { - RunningTime::Short - } -} - -struct NotifyIgnore; - -impl Notify for NotifyIgnore { - fn notify(&self, _: usize) { - // Intentionally ignore - } -} - -static NOTIFY_IGNORE: &&NotifyIgnore = &&NotifyIgnore; - -fn retain_mut(vec: &mut Vec, mut f: F) -where - F: FnMut(&mut T) -> bool, -{ - let len = vec.len(); - let mut del = 0; - { - let v = &mut **vec; - - for i in 0..len { - if !f(&mut v[i]) { - del += 1; - } else if del > 0 { - v.swap(i - del, i); - } - } - } - if del > 0 { - vec.truncate(len - del); - } -} - -#[cfg(test)] -mod test { - use std::error::Error; - use std::fmt::{Display, Formatter, Result as FmtResult}; - - use futures::Poll; - use futures::future::{result, Future, FutureResult}; - use futures::task; - - use common::{BoxedErr, Errors, Merge}; - use shred::DispatcherBuilder; - use storage::{NullStorage, VecStorage}; - use world::{Builder, Component, World}; - - #[test] - fn test_merge() { - #[derive(Default)] - struct TestComponent; - - impl Component for TestComponent { - type Storage = NullStorage; - } - - struct TestFuture { - result: FutureResult, - } - - impl Future for TestFuture { - type Item = TestComponent; - type Error = BoxedErr; - - fn poll(&mut self) -> Poll { - // This function called purely to see if we can. - // Futures will expect to be able to call this function without - // panicking. - task::current(); - self.result.poll() - } - } - - impl Component for TestFuture { - type Storage = VecStorage; - } - - #[derive(Debug)] - struct TestError; - - impl Display for TestError { - fn fmt(&self, fmt: &mut Formatter) -> FmtResult { - fmt.write_str("TestError") - } - } - - impl Error for TestError { - fn description(&self) -> &str { - "An error used for testing" - } - - fn cause(&self) -> Option<&Error> { - None - } - } - - let mut world = World::new(); - world.add_resource(Errors::new()); - world.register::(); - world.register::(); - let success = world - .create_entity() - .with(TestFuture { - result: result(Ok(TestComponent)), - }) - .build(); - let error = world - .create_entity() - .with(TestFuture { - result: result(Err(BoxedErr::new(TestError))), - }) - .build(); - - let system: Merge = Merge::new(); - - let mut dispatcher = DispatcherBuilder::new().with(system, "merge", &[]).build(); - - // Sequential dispatch used in order to avoid missing panics due to them happening in - // another thread. - dispatcher.dispatch_seq(&mut world.res); - let components = world.read_storage::(); - assert!(components.get(success).is_some()); - assert!(components.get(error).is_none()); - assert_eq!( - world.read_resource::().errors.pop().description(), - "An error used for testing" - ); - assert!(world.read_resource::().errors.try_pop().is_none()); - } -} diff --git a/src/lib.rs b/src/lib.rs index 0880c69f1..42e833488 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,18 +199,10 @@ extern crate rayon; extern crate shrev; extern crate tuple_utils; -#[cfg(feature = "common")] -extern crate futures; #[cfg(feature = "serde")] #[macro_use] extern crate serde; -#[cfg(feature = "rudy")] -extern crate rudy; - -#[cfg(feature = "common")] -pub mod common; - #[cfg(feature = "serde")] pub mod saveload; diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 84baa2b7d..7b88c7524 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -7,8 +7,6 @@ pub use self::restrict::{ ImmutableParallelRestriction, MutableParallelRestriction, RestrictedStorage, SequentialRestriction, }; -#[cfg(feature = "rudy")] -pub use self::storages::RudyStorage; pub use self::storages::{BTreeStorage, DenseVecStorage, HashMapStorage, NullStorage, VecStorage}; pub use self::track::{ComponentEvent, Tracked}; pub use self::entry::{Entries, OccupiedEntry, VacantEntry, StorageEntry}; diff --git a/src/storage/storages.rs b/src/storage/storages.rs index 4cbad33c3..d46df0528 100644 --- a/src/storage/storages.rs +++ b/src/storage/storages.rs @@ -8,9 +8,6 @@ use hibitset::BitSetLike; use storage::{DistinctStorage, UnprotectedStorage}; use world::Index; -#[cfg(feature = "rudy")] -use rudy::rudymap::RudyMap; - /// BTreeMap-based storage. #[derive(Derivative)] #[derivative(Default(bound = ""))] @@ -225,39 +222,3 @@ impl UnprotectedStorage for VecStorage { } unsafe impl DistinctStorage for VecStorage {} - -/// Rudy-based storage. -#[cfg(feature = "rudy")] -#[derive(Derivative)] -#[derivative(Default(bound = ""))] -pub struct RudyStorage(RudyMap); - -#[cfg(feature = "rudy")] -impl UnprotectedStorage for RudyStorage { - unsafe fn clean(&mut self, _has: B) - where - B: BitSetLike, - { - } - - unsafe fn get(&self, id: Index) -> &T { - self.0.get(id).unwrap() - } - - unsafe fn get_mut(&mut self, id: Index) -> &mut T { - self.0.get_mut(id).unwrap() - } - - unsafe fn insert(&mut self, id: Index, v: T) { - self.0.insert(id, v); - } - - unsafe fn remove(&mut self, id: Index) -> T { - self.0.remove(id).unwrap() - } -} - -// Rudy does satisfy the DistinctStorage guarantee: -// https://github.com/adevore/rudy/issues/12 -#[cfg(feature = "rudy")] -unsafe impl DistinctStorage for RudyStorage {} diff --git a/src/storage/tests.rs b/src/storage/tests.rs index cb5526b89..6df8db152 100644 --- a/src/storage/tests.rs +++ b/src/storage/tests.rs @@ -200,26 +200,6 @@ mod test { type Storage = BTreeStorage; } - #[derive(PartialEq, Eq, Debug)] - #[cfg(feature = "rudy")] - struct CRudy(u32); - #[cfg(feature = "rudy")] - impl From for CRudy { - fn from(v: u32) -> CRudy { - CRudy(v) - } - } - #[cfg(feature = "rudy")] - impl AsMut for CRudy { - fn as_mut(&mut self) -> &mut u32 { - &mut self.0 - } - } - #[cfg(feature = "rudy")] - impl Component for CRudy { - type Storage = RudyStorage; - } - #[derive(Debug, Default, PartialEq)] struct Cnull; @@ -496,37 +476,6 @@ mod test { test_clear::(); } - #[cfg(feature = "rudy")] - #[test] - fn rudy_test_add() { - test_add::(); - } - #[cfg(feature = "rudy")] - #[test] - fn rudy_test_sub() { - test_sub::(); - } - #[cfg(feature = "rudy")] - #[test] - fn rudy_test_get_mut() { - test_get_mut::(); - } - #[cfg(feature = "rudy")] - #[test] - fn rudy_test_add_gen() { - test_add_gen::(); - } - #[cfg(feature = "rudy")] - #[test] - fn rudy_test_sub_gen() { - test_sub_gen::(); - } - #[cfg(feature = "rudy")] - #[test] - fn rudy_test_clear() { - test_clear::(); - } - #[test] fn dummy_test_clear() { test_clear::();