Skip to content

Commit

Permalink
component: Use generational-arena for component storage
Browse files Browse the repository at this point in the history
The `generational-arena` crate implements a generational index allocator
which provides a "weak ref" for addressing a particular component,
implemented in this PR in the form of `component::Handle`:

<https://github.com/fitzgen/generational-arena>

This also allows multiple simultaneous mutable references to components,
so this PR additionally introduces `Component::register_dependency`
which provides mutable refs to a component and its dependency for each
enumerated dependency. In principle this is equivalent to "dependency
injection".

This PR removes the previous downcasting functionality, which was never
tested and didn't seem to work out in practice. It should get added
back, but in a form that actually works, with tests.
  • Loading branch information
tony-iqlusion committed Aug 2, 2019
1 parent 776398f commit d15c147
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 138 deletions.
12 changes: 11 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 25 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,27 +87,28 @@ default set of features in the application:
| 6 | [canonical-path] | [iqlusion] | Apache-2.0 | Get canonical fs paths |
| 7 | [chrono] | [chronotope] | Apache-2.0/MIT | Time/date library |
| 8 | [failure] | [@withoutboats] | Apache-2.0/MIT | Error handling |
| 9 | [gumdrop] | [@Murarth] | Apache-2.0/MIT | Command-line options |
| 10 | [lazy_static] | [rust-lang] | Apache-2.0/MIT | Heap-allocated statics |
| 11 | [libc] | [rust-lang] | Apache-2.0/MIT | C library wrapper |
| 12 | [log] | [rust-lang] | Apache-2.0/MIT | Logging facade library |
| 13 | [num-integer] | [rust-num] | Apache-2.0/MIT | `Integer` trait |
| 14 | [num-traits] | [rust-num] | Apache-2.0/MIT | Numeric traits |
| 15 | [redox_syscall] | [redox-os] | MIT | Redox OS syscall API |
| 16 | [rustc-demangle] | [@alexcrichton] | Apache-2.0/MIT | Symbol demangling |
| 17 | [secrecy] | [iqlusion] | Apache-2.0 | Secret-keeping types |
| 18 | [semver] | [@steveklabnik] | Apache-2.0/MIT | Semantic versioning |
| 19 | [semver-parser] | [@steveklabnik] | Apache-2.0/MIT | Parser for semver spec |
| 20 | [serde] | [serde-rs] | Apache-2.0/MIT | Serialization framework |
| 21 | [signal-hook] | [@vorner] | Apache-2.0/MIT | Unix signal handling |
| 22 | [signal-hook-registry] | [@vorner] | Apache-2.0/MIT | Unix signal registry |
| 23 | [termcolor] | [@BurntSushi] | MIT/Unlicense | Terminal color support |
| 24 | [time] | [rust-lang] | Apache-2.0/MIT | Time/date library |
| 25 | [toml] | [@alexcrichton] | Apache-2.0/MIT | TOML parser library |
| 26 | [winapi]§ | [@retep998] | Apache-2.0/MIT | Windows FFI bindings |
| 27 | [winapi-util] | [@BurntSushi] | MIT/Unlicense | Safe winapi wrappers |
| 28 | [wincolor] | [@BurntSushi] | MIT/Unlicense | Windows console color |
| 29 | [zeroize] | [iqlusion] | Apache-2.0/MIT | Zero out sensitive data |
| 9 | [generational-arena] | [@fitzgen] | MPL-2.0 | Component allocator |
| 10 | [gumdrop] | [@Murarth] | Apache-2.0/MIT | Command-line options |
| 11 | [lazy_static] | [rust-lang] | Apache-2.0/MIT | Heap-allocated statics |
| 12 | [libc] | [rust-lang] | Apache-2.0/MIT | C library wrapper |
| 13 | [log] | [rust-lang] | Apache-2.0/MIT | Logging facade library |
| 14 | [num-integer] | [rust-num] | Apache-2.0/MIT | `Integer` trait |
| 15 | [num-traits] | [rust-num] | Apache-2.0/MIT | Numeric traits |
| 16 | [redox_syscall] | [redox-os] | MIT | Redox OS syscall API |
| 17 | [rustc-demangle] | [@alexcrichton] | Apache-2.0/MIT | Symbol demangling |
| 18 | [secrecy] | [iqlusion] | Apache-2.0 | Secret-keeping types |
| 19 | [semver] | [@steveklabnik] | Apache-2.0/MIT | Semantic versioning |
| 20 | [semver-parser] | [@steveklabnik] | Apache-2.0/MIT | Parser for semver spec |
| 21 | [serde] | [serde-rs] | Apache-2.0/MIT | Serialization framework |
| 22 | [signal-hook] | [@vorner] | Apache-2.0/MIT | Unix signal handling |
| 23 | [signal-hook-registry] | [@vorner] | Apache-2.0/MIT | Unix signal registry |
| 24 | [termcolor] | [@BurntSushi] | MIT/Unlicense | Terminal color support |
| 25 | [time] | [rust-lang] | Apache-2.0/MIT | Time/date library |
| 26 | [toml] | [@alexcrichton] | Apache-2.0/MIT | TOML parser library |
| 27 | [winapi]§ | [@retep998] | Apache-2.0/MIT | Windows FFI bindings |
| 28 | [winapi-util] | [@BurntSushi] | MIT/Unlicense | Safe winapi wrappers |
| 29 | [wincolor] | [@BurntSushi] | MIT/Unlicense | Windows console color |
| 30 | [zeroize] | [iqlusion] | Apache-2.0/MIT | Zero out sensitive data |

### Build / Development / Testing Dependencies

Expand Down Expand Up @@ -156,6 +157,7 @@ so you only compile the parts you need.
| [chrono] | `time` | [abscissa] |
| [failure] | - | [abscissa] |
| [failure_derive] | - | [failure] |
| [generational-arena] | `application` | [abscissa] |
| [gumdrop] | `options` | [abscissa] |
| [gumdrop_derive] | `options` | [gumdrop] |
| [heck] | `inflector` | [abscissa] |
Expand Down Expand Up @@ -323,6 +325,7 @@ read the [CONTRIBUTING.md] and [CODE_OF_CONDUCT.md] files first.
[chrono]: https://crates.io/crates/chrono
[failure]: https://crates.io/crates/failure
[failure_derive]: https://crates.io/crates/failure_derive
[generational-arena]: https://github.com/fitzgen/generational-arena
[gumdrop]: https://crates.io/crates/gumdrop
[gumdrop_derive]: https://crates.io/crates/gumdrop_derive
[heck]: https://crates.io/crates/heck
Expand Down Expand Up @@ -369,6 +372,7 @@ read the [CONTRIBUTING.md] and [CODE_OF_CONDUCT.md] files first.
[@BurntSushi]: https://github.com/BurntSushi
[@cuviper]: https://github.com/cuviper
[@dtolnay]: https://github.com/dtolnay
[@fitzgen]: https://github.com/fitzgen
[@Murarth]: https://github.com/Murarth
[@mystor]: https://github.com/mystor
[@retep998]: https://github.com/retep998
Expand Down
4 changes: 2 additions & 2 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description = """
configuration, error handling, logging, and terminal interactions.
This crate contains a CLI utility for generating new applications.
"""
version = "0.2.1" # Also update html_root_url in lib.rs when bumping this
version = "0.3.0-rc.0" # Also update html_root_url in lib.rs when bumping this
license = "Apache-2.0"
authors = ["Tony Arcieri <[email protected]>"]
edition = "2018"
Expand All @@ -27,7 +27,7 @@ lazy_static = "1"
serde = { version = "1", features = ["serde_derive"] }

[dependencies.abscissa_core]
version = "0.2"
version = "0.3.0-rc.0"
path = "../core"

[dev-dependencies.abscissa_core]
Expand Down
5 changes: 1 addition & 4 deletions cli/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,7 @@ impl Application for CliApplication {
}

fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError> {
for component in self.state.components.iter_mut() {
component.after_config(&config)?;
}

self.state.components.after_config(&config)?;
self.config = Some(config);
Ok(())
}
Expand Down
7 changes: 4 additions & 3 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description = """
configuration, error handling, logging, and terminal interactions.
This crate contains the framework's core functionality.
"""
version = "0.2.1" # Also update html_root_url in lib.rs when bumping this
version = "0.3.0-rc.0" # Also update html_root_url in lib.rs when bumping this
license = "Apache-2.0"
authors = ["Tony Arcieri <[email protected]>"]
edition = "2018"
Expand All @@ -16,10 +16,11 @@ categories = ["command-line-interface", "rust-patterns"]
keywords = ["abscissa", "cli", "application", "framework", "service"]

[dependencies]
abscissa_derive = { version = "0.2", path = "../derive" }
abscissa_derive = { version = "0.3.0-rc.0", path = "../derive" }
canonical-path = "2"
chrono = { version = "0.4", optional = true, features = ["serde"] }
failure = "0.1"
generational-arena = { version = "0.2", optional = true }
gumdrop = { version = "0.6", optional = true }
lazy_static = "1"
log = { version = "0.4", optional = true, features = ["std"] }
Expand All @@ -36,7 +37,7 @@ libc = { version = "0.2", optional = true }
signal-hook = { version = "0.1", optional = true }

[features]
default = ["application", "signals", "secrets", "testing", "time"]
default = ["application", "generational-arena", "signals", "secrets", "testing", "time"]
application = ["config", "logging", "options", "semver/serde", "terminal"]
config = ["secrets", "serde", "terminal", "toml"]
logging = ["log"]
Expand Down
32 changes: 23 additions & 9 deletions core/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

#![allow(unused_variables)]

mod name;
mod handle;
mod id;
mod registry;

pub use self::{name::Name, registry::Registry};
pub use self::{handle::Handle, id::Id, registry::Registry};
use crate::{application::Application, error::FrameworkError, shutdown::Shutdown, Version};
use std::{cmp::Ordering, fmt::Debug, slice::Iter};

Expand All @@ -25,14 +26,16 @@ pub trait Component<A>: Debug + Send + Sync
where
A: Application,
{
/// Name of this component
fn name(&self) -> Name;
/// Identifier for this component.
///
/// These are the Rust path (e.g. `crate_name:foo::Foo`) by convention.
fn id(&self) -> Id;

/// Version of this component
fn version(&self) -> Version;

/// Names of the components this components depends on
fn dependencies(&self) -> Iter<'_, Name> {
fn dependencies(&self) -> Iter<'_, Id> {
[].iter()
}

Expand All @@ -42,6 +45,17 @@ where
Ok(())
}

/// Register a dependency of this component (a.k.a. "dependency injection")
// TODO(tarcieri): fix clippy warning
#[allow(clippy::borrowed_box)]
fn register_dependency(
&mut self,
handle: Handle,
dependency: &mut Box<dyn Component<A>>,
) -> Result<(), FrameworkError> {
Ok(())
}

/// Perform any tasks which should occur before the app exits
fn before_shutdown(&self, kind: Shutdown) -> Result<(), FrameworkError> {
Ok(())
Expand All @@ -53,7 +67,7 @@ where
A: Application,
{
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
self.id() == other.id()
}
}

Expand All @@ -62,13 +76,13 @@ where
A: Application,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if other.dependencies().any(|dep| *dep == self.name()) {
if self.dependencies().any(|dep| *dep == other.name()) {
if other.dependencies().any(|dep| *dep == self.id()) {
if self.dependencies().any(|dep| *dep == other.id()) {
None
} else {
Some(Ordering::Greater)
}
} else if self.dependencies().any(|dep| *dep == other.name()) {
} else if self.dependencies().any(|dep| *dep == other.id()) {
Some(Ordering::Less)
} else {
Some(Ordering::Equal)
Expand Down
38 changes: 38 additions & 0 deletions core/src/component/handle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Component handles: opaque references to registered components

use super::id::Id;
use generational_arena::Index;
use std::fmt;

/// Component handles are references to components which have been registered
/// with a `component::Registry`.
///
/// However, unlike normal Rust references, component handles are a "weak"
/// reference which is not checked by the borrow checker. This allows for
/// complex reference graphs which are otherwise inexpressible in Rust.
#[derive(Copy, Clone, Eq, Hash, PartialEq, Ord, PartialOrd)]
pub struct Handle {
/// Component name
name: Id,

/// Index in the registry's generational index allocator
pub(super) index: Index,
}

impl Handle {
/// Create a new handle from a component's name and index
pub(crate) fn new(name: Id, index: Index) -> Self {
Self { name, index }
}

/// Get the identifier of the component this handle points to
pub fn id(self) -> Id {
self.name
}
}

impl fmt::Debug for Handle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Handle({})", self.id().as_ref())
}
}
33 changes: 33 additions & 0 deletions core/src/component/id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Component identifiers
//!
//! By convention these are Rust paths to the component types
// TODO(tarcieri): enforce this convention via e.g. custom derive?

use std::fmt;

/// Identifier for an individual component
///
/// This should ideally match the Rust path name to the corresponding type.
// TODO(tarcieri): obtain this automatically via `std::module_path`?
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
pub struct Id(&'static str);

impl Id {
/// Create a new component identifier
// TODO(tarcieri): make this method private in the future
pub fn new(id: &'static str) -> Id {
Id(id)
}
}

impl AsRef<str> for Id {
fn as_ref(&self) -> &str {
self.0
}
}

impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
22 changes: 0 additions & 22 deletions core/src/component/name.rs

This file was deleted.

Loading

0 comments on commit d15c147

Please sign in to comment.