From b42f96c350c034c17dc9f682e5edbc600694077c Mon Sep 17 00:00:00 2001 From: torkleyy Date: Sat, 4 Mar 2017 16:23:12 +0100 Subject: [PATCH 1/3] Add PlannerBuilder --- Cargo.toml | 3 +- src/lib.rs | 2 + src/planner.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 117 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d02797c0..0b301511b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,11 @@ exclude = ["doc", ".travis.yml"] mopa = "0.2" pulse = { version = "0.5", optional = true } threadpool = { version = "1.3", optional = true } +num_cpus = { version = "1.0", optional = true } fnv = "1.0" tuple_utils="0.2" atom = "0.3" [features] default = ["parallel"] -parallel = ["threadpool", "pulse"] +parallel = ["threadpool", "pulse", "num_cpus"] diff --git a/src/lib.rs b/src/lib.rs index 88cee0484..814873ad3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,8 @@ extern crate mopa; extern crate pulse; #[cfg(feature="parallel")] extern crate threadpool; +#[cfg(feature="parallel")] +extern crate num_cpus; extern crate fnv; extern crate tuple_utils; extern crate atom; diff --git a/src/planner.rs b/src/planner.rs index c4ac9940b..17cde606d 100644 --- a/src/planner.rs +++ b/src/planner.rs @@ -3,6 +3,7 @@ use std::sync::{mpsc, Arc}; use pulse::{Pulse, Signal}; use threadpool::ThreadPool; +use num_cpus::get as get_num_cpus; use super::{Component, JoinIter, World, Entity}; @@ -92,6 +93,113 @@ impl Drop for SystemGuard { } } +/// A builder for the `Planner` struct. +/// All `with_*` methods return `self` to allow chained calls. +/// +/// ## World +/// +/// You have to specify a world, otherwise the +/// `build` method will panic. +/// +/// ## Number of threads +/// +/// Specifying the number of threads to use +/// for the thread pool is optional. +/// +/// **Note:** Only specify this if you do not +/// want to share the thread pool, because then +/// the number of threads the pool is configured with +/// will determince this property. +/// +/// # The thread pool +/// +/// Also optional, use this if you want to +/// share the thread pool with other parts of +/// your crate (you should do this if you use +/// threads somwhere else). +/// +/// If you didn't specify a thread pool nor a number of +/// threads, a new thread pool with the number of virtual +/// cpus will be created. +pub struct PlannerBuilder { + world: Option, + num_threads: Option, + thread_pool: Option>, +} + +impl PlannerBuilder { + /// Creates a new `PlannerBuilder`. + pub fn new() -> Self { + PlannerBuilder { + world: None, + num_threads: None, + thread_pool: None, + } + } + + /// Use this to specify the world for the planner. + /// + /// Note that this is required for the creation of a `Planner`. + pub fn with_world(mut self, world: World) -> Self { + self.world = Some(world); + + self + } + + /// This is an optional property and should only be used + /// if you want to create a thread pool for this planner. + /// + /// Also see the documentation of the `PlannerBuilder` struct. + /// + /// # Panics + /// + /// * Panics if you already used `with_thread_pool`. + pub fn with_num_threads(mut self, num_threads: usize) -> Self { + assert!(self.thread_pool.is_none()); + + self.num_threads = Some(num_threads); + + self + } + + /// Share a thread pool with this planner. It is recommended to + /// use this over `with_num_threads` (if you use threads anywhere else). + /// + /// # Panics + /// + /// * Panics if you already used `with_num_threads`. + pub fn with_thread_pool(mut self, thread_pool: Arc) -> Self { + assert!(self.num_threads.is_none()); + + self.thread_pool = Some(thread_pool); + + self + } + + /// Builds a planner from the specified properties. + /// + /// # Panics + /// + /// * Panics if no world was specified + pub fn build(self) -> Planner { + let PlannerBuilder { world, num_threads, thread_pool } = self; + let (sout, sin) = mpsc::channel(); + + let threader = thread_pool.unwrap_or_else(|| { + Arc::new(ThreadPool::new(num_threads.unwrap_or(get_num_cpus()))) + }); + + Planner { + world: Arc::new(world.expect("A world is required for planner creation")), + systems: Vec::new(), + wait_count: 0, + chan_out: sout, + chan_in: sin, + threader: threader, + } + } +} + /// System execution planner. Allows running systems via closures, /// distributes the load in parallel using a thread pool. pub struct Planner { @@ -102,21 +210,16 @@ pub struct Planner { wait_count: usize, chan_out: mpsc::Sender>, chan_in: mpsc::Receiver>, - threader: ThreadPool, + threader: Arc, } impl Planner { /// Creates a new planner, given the world and the thread count. pub fn new(world: World, num_threads: usize) -> Planner { - let (sout, sin) = mpsc::channel(); - Planner { - world: Arc::new(world), - systems: Vec::new(), - wait_count: 0, - chan_out: sout, - chan_in: sin, - threader: ThreadPool::new(num_threads), - } + PlannerBuilder::new() + .with_world(world) + .with_num_threads(num_threads) + .build() } /// Add a system to the dispatched list. pub fn add_system(&mut self, sys: S, name: &str, priority: Priority) where From eea22ee3c2e49b4266ce92f1b8d1f2d082aff88e Mon Sep 17 00:00:00 2001 From: torkleyy Date: Sat, 4 Mar 2017 16:27:59 +0100 Subject: [PATCH 2/3] Add reference to builder in Planner::new --- src/planner.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/planner.rs b/src/planner.rs index 17cde606d..e05ac3241 100644 --- a/src/planner.rs +++ b/src/planner.rs @@ -215,6 +215,8 @@ pub struct Planner { impl Planner { /// Creates a new planner, given the world and the thread count. + /// + /// For a more flexible creation, see the `PlannerBuilder`. pub fn new(world: World, num_threads: usize) -> Planner { PlannerBuilder::new() .with_world(world) From 50b8ea3e03df6ce93d9ec7704feab54b8da70fcf Mon Sep 17 00:00:00 2001 From: torkleyy Date: Mon, 6 Mar 2017 14:50:27 +0100 Subject: [PATCH 3/3] Remove planner builder and use PoolSize trait instead --- src/planner.rs | 170 ++++++++++++++++++++----------------------------- 1 file changed, 69 insertions(+), 101 deletions(-) diff --git a/src/planner.rs b/src/planner.rs index e05ac3241..7061917b6 100644 --- a/src/planner.rs +++ b/src/planner.rs @@ -93,110 +93,41 @@ impl Drop for SystemGuard { } } -/// A builder for the `Planner` struct. -/// All `with_*` methods return `self` to allow chained calls. -/// -/// ## World +/// A trait which should be implemented +/// for values which give you a pool size. /// -/// You have to specify a world, otherwise the -/// `build` method will panic. -/// -/// ## Number of threads -/// -/// Specifying the number of threads to use -/// for the thread pool is optional. -/// -/// **Note:** Only specify this if you do not -/// want to share the thread pool, because then -/// the number of threads the pool is configured with -/// will determince this property. -/// -/// # The thread pool -/// -/// Also optional, use this if you want to -/// share the thread pool with other parts of -/// your crate (you should do this if you use -/// threads somwhere else). -/// -/// If you didn't specify a thread pool nor a number of -/// threads, a new thread pool with the number of virtual -/// cpus will be created. -pub struct PlannerBuilder { - world: Option, - num_threads: Option, - thread_pool: Option>, +/// Implemented for `i32`, `usize` and +/// `NumCpus`. +pub trait PoolSize { + /// Returns how many threads the thread pool + /// should have. + fn size(self) -> usize; } -impl PlannerBuilder { - /// Creates a new `PlannerBuilder`. - pub fn new() -> Self { - PlannerBuilder { - world: None, - num_threads: None, - thread_pool: None, - } - } - - /// Use this to specify the world for the planner. - /// - /// Note that this is required for the creation of a `Planner`. - pub fn with_world(mut self, world: World) -> Self { - self.world = Some(world); - - self - } - - /// This is an optional property and should only be used - /// if you want to create a thread pool for this planner. - /// - /// Also see the documentation of the `PlannerBuilder` struct. - /// - /// # Panics - /// - /// * Panics if you already used `with_thread_pool`. - pub fn with_num_threads(mut self, num_threads: usize) -> Self { - assert!(self.thread_pool.is_none()); - - self.num_threads = Some(num_threads); - +impl PoolSize for usize { + fn size(self) -> usize { self } +} - /// Share a thread pool with this planner. It is recommended to - /// use this over `with_num_threads` (if you use threads anywhere else). - /// - /// # Panics - /// - /// * Panics if you already used `with_num_threads`. - pub fn with_thread_pool(mut self, thread_pool: Arc) -> Self { - assert!(self.num_threads.is_none()); - - self.thread_pool = Some(thread_pool); - - self +impl PoolSize for i32 { + fn size(self) -> usize { + self as usize } +} - /// Builds a planner from the specified properties. - /// - /// # Panics - /// - /// * Panics if no world was specified - pub fn build(self) -> Planner { - let PlannerBuilder { world, num_threads, thread_pool } = self; - let (sout, sin) = mpsc::channel(); - - let threader = thread_pool.unwrap_or_else(|| { - Arc::new(ThreadPool::new(num_threads.unwrap_or(get_num_cpus()))) - }); +/// This is an empty struct, which +/// implements `Into` which +/// returns the number of virtual +/// threads. +/// +/// Intended to be used with +/// `Planner::new`. +pub struct NumCpus; - Planner { - world: Arc::new(world.expect("A world is required for planner creation")), - systems: Vec::new(), - wait_count: 0, - chan_out: sout, - chan_in: sin, - threader: threader, - } +impl PoolSize for NumCpus { + fn size(self) -> usize { + get_num_cpus() } } @@ -215,14 +146,51 @@ pub struct Planner { impl Planner { /// Creates a new planner, given the world and the thread count. + /// If you already have a `ThreadPool`, consider using `from_pool` instead. + /// + /// For the thread count, you can pass anything implementing `Into`. + /// + /// # Examples + /// + /// You can just pass a world and a number + /// of threads to use for the threadpool. + /// + /// ``` + /// use specs::{NumCpus, Planner, World}; /// - /// For a more flexible creation, see the `PlannerBuilder`. - pub fn new(world: World, num_threads: usize) -> Planner { - PlannerBuilder::new() - .with_world(world) - .with_num_threads(num_threads) - .build() + /// # let world = World::new_w_comp_id(); + /// let planner: Planner<()> = Planner::new(world, 4); + /// ``` + /// + /// But if you don't want to hardcode the number + /// of threads, you can pass `NumCpus`: + /// + /// ``` + /// use specs::{NumCpus, Planner, World}; + /// + /// # let world = World::new_w_comp_id(); + /// let planner: Planner<()> = Planner::new(world, NumCpus); + /// ``` + /// + pub fn new(world: World, num_threads: N) -> Planner { + Self::from_pool(world, Arc::new(ThreadPool::new(num_threads.size()))) } + + /// Creates a new `Planner` from a given + /// thread pool. + pub fn from_pool(world: World, pool: Arc) -> Planner { + let (cout, cin) = mpsc::channel(); + + Planner { + world: Arc::new(world), + systems: Vec::new(), + wait_count: 0, + chan_out: cout, + chan_in: cin, + threader: pool, + } + } + /// Add a system to the dispatched list. pub fn add_system(&mut self, sys: S, name: &str, priority: Priority) where S: 'static + System