-
Notifications
You must be signed in to change notification settings - Fork 292
Rework container traits #686
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
0d03378
be571f3
ba7abf7
0cdf9de
3f78d6b
ef54810
236c46b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,46 +4,34 @@ | |
|
|
||
| use std::collections::VecDeque; | ||
|
|
||
| /// A container transferring data through dataflow edges | ||
| /// A type representing progress, with an update count. | ||
| /// | ||
| /// A container stores a number of elements and thus is able to describe it length (`len()`) and | ||
| /// whether it is empty (`is_empty()`). It supports removing all elements (`clear`). | ||
| /// It describes its update count (`count()`) and whether it is empty (`is_empty()`). | ||
| /// | ||
| /// A container must implement default. The default implementation is not required to allocate | ||
| /// memory for variable-length components. | ||
| /// | ||
| /// We require the container to be cloneable to enable efficient copies when providing references | ||
| /// of containers to operators. Care must be taken that the type's `clone_from` implementation | ||
| /// is efficient (which is not necessarily the case when deriving `Clone`.) | ||
| pub trait Container: Default { | ||
| /// The type of elements when reading non-destructively from the container. | ||
| type ItemRef<'a> where Self: 'a; | ||
|
|
||
| /// The type of elements when draining the container. | ||
| type Item<'a> where Self: 'a; | ||
|
|
||
| /// Push `item` into self | ||
| #[inline] | ||
| fn push<T>(&mut self, item: T) where Self: PushInto<T> { | ||
| self.push_into(item) | ||
| } | ||
|
Comment on lines
-27
to
-29
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| /// We require [`Default`] for convenience purposes. | ||
| pub trait WithProgress: Default { | ||
|
||
| /// The number of elements in this container | ||
| /// | ||
| /// This number is used in progress tracking to confirm the receipt of some number | ||
| /// of outstanding records, and it is highly load bearing. The main restriction is | ||
| /// imposed on the `LengthPreservingContainerBuilder` trait, whose implementors | ||
| /// imposed on the [`CountPreservingContainerBuilder`] trait, whose implementors | ||
| /// must preserve the number of items. | ||
| fn len(&self) -> usize; | ||
| fn count(&self) -> usize; | ||
|
|
||
| /// Determine if the container contains any elements, corresponding to `len() == 0`. | ||
| /// Determine if the container contains any elements, corresponding to `count() == 0`. | ||
| #[inline(always)] | ||
| fn is_empty(&self) -> bool { | ||
|
||
| self.len() == 0 | ||
| self.count() == 0 | ||
| } | ||
| } | ||
|
|
||
| /// Remove all contents from `self` while retaining allocated memory. | ||
| /// After calling `clear`, `is_empty` must return `true` and `len` 0. | ||
| fn clear(&mut self); | ||
| /// A container that can reveal its contents through iterating by reference and draining. | ||
| pub trait IterableContainer: WithProgress { | ||
|
||
| /// The type of elements when reading non-destructively from the container. | ||
| type ItemRef<'a> where Self: 'a; | ||
|
|
||
| /// The type of elements when draining the container. | ||
| type Item<'a> where Self: 'a; | ||
|
|
||
| /// Iterator type when reading from the container. | ||
| type Iter<'a>: Iterator<Item=Self::ItemRef<'a>> where Self: 'a; | ||
|
|
@@ -60,7 +48,7 @@ pub trait Container: Default { | |
| } | ||
|
|
||
| /// A container that can be sized and reveals its capacity. | ||
| pub trait SizableContainer: Container { | ||
| pub trait SizableContainer: WithProgress { | ||
| /// Indicates that the container is "full" and should be shipped. | ||
| fn at_capacity(&self) -> bool; | ||
| /// Restores `self` to its desired capacity, if it has one. | ||
|
|
@@ -102,7 +90,7 @@ pub trait PushInto<T> { | |
| /// decide to represent a push order for `extract` and `finish`, or not. | ||
| pub trait ContainerBuilder: Default + 'static { | ||
| /// The container type we're building. | ||
| type Container: Container + Clone + 'static; | ||
| type Container: WithProgress + Default + Clone + 'static; | ||
|
||
| /// Extract assembled containers, potentially leaving unfinished data behind. Can | ||
| /// be called repeatedly, for example while the caller can send data. | ||
| /// | ||
|
|
@@ -116,14 +104,14 @@ pub trait ContainerBuilder: Default + 'static { | |
| /// Partitions `container` among `builders`, using the function `index` to direct items. | ||
| fn partition<I>(container: &mut Self::Container, builders: &mut [Self], mut index: I) | ||
| where | ||
| Self: for<'a> PushInto<<Self::Container as Container>::Item<'a>>, | ||
| I: for<'a> FnMut(&<Self::Container as Container>::Item<'a>) -> usize, | ||
| Self: for<'a> PushInto<<Self::Container as IterableContainer>::Item<'a>>, | ||
| I: for<'a> FnMut(&<Self::Container as IterableContainer>::Item<'a>) -> usize, | ||
| Self::Container: IterableContainer, | ||
| { | ||
| for datum in container.drain() { | ||
| let index = index(&datum); | ||
| builders[index].push_into(datum); | ||
| } | ||
| container.clear(); | ||
| } | ||
|
|
||
| /// Indicates a good moment to release resources. | ||
|
|
@@ -140,7 +128,34 @@ pub trait ContainerBuilder: Default + 'static { | |
| /// Specifically, the sum of lengths of all extracted and finished containers must equal the | ||
| /// number of times that `push_into` is called on the container builder. | ||
| /// If you have any questions about this trait you are best off not implementing it. | ||
| pub trait LengthPreservingContainerBuilder : ContainerBuilder { } | ||
| pub trait CountPreservingContainerBuilder: ContainerBuilder { } | ||
|
||
|
|
||
| /// A container builder that never produces any outputs, and can be used to pass through data in | ||
| /// operators. | ||
| #[derive(Debug, Clone)] | ||
| pub struct PassthroughContainerBuilder<C>(std::marker::PhantomData<C>); | ||
|
|
||
| impl<C> Default for PassthroughContainerBuilder<C> { | ||
| #[inline(always)] | ||
| fn default() -> Self { | ||
| PassthroughContainerBuilder(std::marker::PhantomData) | ||
| } | ||
| } | ||
|
|
||
| impl<C: WithProgress + Clone + 'static> ContainerBuilder for PassthroughContainerBuilder<C> | ||
| { | ||
| type Container = C; | ||
|
|
||
| #[inline(always)] | ||
| fn extract(&mut self) -> Option<&mut Self::Container> { | ||
| None | ||
| } | ||
|
|
||
| #[inline(always)] | ||
| fn finish(&mut self) -> Option<&mut Self::Container> { | ||
| None | ||
| } | ||
| } | ||
|
|
||
| /// A default container builder that uses length and preferred capacity to chunk data. | ||
| /// | ||
|
|
@@ -165,7 +180,7 @@ impl<T, C: SizableContainer + PushInto<T>> PushInto<T> for CapacityContainerBuil | |
| self.current.ensure_capacity(&mut self.empty); | ||
|
|
||
| // Push item | ||
| self.current.push(item); | ||
| self.current.push_into(item); | ||
antiguru marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Maybe flush | ||
| if self.current.at_capacity() { | ||
|
|
@@ -174,7 +189,7 @@ impl<T, C: SizableContainer + PushInto<T>> PushInto<T> for CapacityContainerBuil | |
| } | ||
| } | ||
|
|
||
| impl<C: Container + Clone + 'static> ContainerBuilder for CapacityContainerBuilder<C> { | ||
| impl<C: WithProgress + Clone + 'static> ContainerBuilder for CapacityContainerBuilder<C> { | ||
| type Container = C; | ||
|
|
||
| #[inline] | ||
|
|
@@ -197,22 +212,11 @@ impl<C: Container + Clone + 'static> ContainerBuilder for CapacityContainerBuild | |
| } | ||
| } | ||
|
|
||
| impl<C: Container + Clone + 'static> LengthPreservingContainerBuilder for CapacityContainerBuilder<C> { } | ||
| impl<C: WithProgress + SizableContainer + Clone + 'static> CountPreservingContainerBuilder for CapacityContainerBuilder<C> { } | ||
|
|
||
| impl<T> Container for Vec<T> { | ||
| impl<T> IterableContainer for Vec<T> { | ||
| type ItemRef<'a> = &'a T where T: 'a; | ||
| type Item<'a> = T where T: 'a; | ||
|
|
||
| fn len(&self) -> usize { | ||
| Vec::len(self) | ||
| } | ||
|
|
||
| fn is_empty(&self) -> bool { | ||
| Vec::is_empty(self) | ||
| } | ||
|
|
||
| fn clear(&mut self) { Vec::clear(self) } | ||
|
|
||
| type Iter<'a> = std::slice::Iter<'a, T> where Self: 'a; | ||
|
|
||
| fn iter(&self) -> Self::Iter<'_> { | ||
|
|
@@ -268,29 +272,11 @@ mod rc { | |
| use std::ops::Deref; | ||
| use std::rc::Rc; | ||
|
|
||
| use crate::Container; | ||
| use crate::IterableContainer; | ||
|
|
||
| impl<T: Container> Container for Rc<T> { | ||
| impl<T: IterableContainer> IterableContainer for Rc<T> { | ||
| type ItemRef<'a> = T::ItemRef<'a> where Self: 'a; | ||
| type Item<'a> = T::ItemRef<'a> where Self: 'a; | ||
|
|
||
| fn len(&self) -> usize { | ||
| std::ops::Deref::deref(self).len() | ||
| } | ||
|
|
||
| fn is_empty(&self) -> bool { | ||
| std::ops::Deref::deref(self).is_empty() | ||
| } | ||
|
|
||
| fn clear(&mut self) { | ||
| // Try to reuse the allocation if possible | ||
| if let Some(inner) = Rc::get_mut(self) { | ||
| inner.clear(); | ||
| } else { | ||
| *self = Self::default(); | ||
| } | ||
| } | ||
|
|
||
| type Iter<'a> = T::Iter<'a> where Self: 'a; | ||
|
|
||
| fn iter(&self) -> Self::Iter<'_> { | ||
|
|
@@ -309,29 +295,11 @@ mod arc { | |
| use std::ops::Deref; | ||
| use std::sync::Arc; | ||
|
|
||
| use crate::Container; | ||
| use crate::IterableContainer; | ||
|
|
||
| impl<T: Container> Container for Arc<T> { | ||
| impl<T: IterableContainer> IterableContainer for Arc<T> { | ||
| type ItemRef<'a> = T::ItemRef<'a> where Self: 'a; | ||
| type Item<'a> = T::ItemRef<'a> where Self: 'a; | ||
|
|
||
| fn len(&self) -> usize { | ||
| std::ops::Deref::deref(self).len() | ||
| } | ||
|
|
||
| fn is_empty(&self) -> bool { | ||
| std::ops::Deref::deref(self).is_empty() | ||
| } | ||
|
|
||
| fn clear(&mut self) { | ||
| // Try to reuse the allocation if possible | ||
| if let Some(inner) = Arc::get_mut(self) { | ||
| inner.clear(); | ||
| } else { | ||
| *self = Self::default(); | ||
| } | ||
| } | ||
|
|
||
| type Iter<'a> = T::Iter<'a> where Self: 'a; | ||
|
|
||
| fn iter(&self) -> Self::Iter<'_> { | ||
|
|
@@ -366,3 +334,18 @@ pub mod buffer { | |
| } | ||
| } | ||
| } | ||
|
|
||
| impl<T> WithProgress for Vec<T> { | ||
| #[inline(always)] fn count(&self) -> usize { self.len() } | ||
| #[inline(always)] fn is_empty(&self) -> bool { Vec::is_empty(self) } | ||
| } | ||
|
|
||
| impl<T: WithProgress> WithProgress for std::rc::Rc<T> { | ||
| #[inline(always)] fn count(&self) -> usize { self.as_ref().count() } | ||
| #[inline(always)] fn is_empty(&self) -> bool { self.as_ref().is_empty() } | ||
| } | ||
|
|
||
| impl<T: WithProgress> WithProgress for std::sync::Arc<T> { | ||
| #[inline(always)] fn count(&self) -> usize { self.as_ref().count() } | ||
| #[inline(always)] fn is_empty(&self) -> bool { self.as_ref().is_empty() } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
|
|
||
| use serde::{Deserialize, Serialize}; | ||
| use crate::communication::Push; | ||
| use crate::Container; | ||
| use crate::container::WithProgress; | ||
|
|
||
| /// A collection of types that may be pushed at. | ||
| pub mod pushers; | ||
|
|
@@ -32,14 +32,15 @@ impl<T, C> Message<T, C> { | |
| } | ||
| } | ||
|
|
||
| impl<T, C: Container> Message<T, C> { | ||
| impl<T, C: WithProgress> Message<T, C> { | ||
| /// Creates a new message instance from arguments. | ||
| pub fn new(time: T, data: C, from: usize, seq: usize) -> Self { | ||
| Message { time, data, from, seq } | ||
| } | ||
|
|
||
| /// Forms a message, and pushes contents at `pusher`. Replaces `buffer` with what the pusher | ||
| /// leaves in place, or the container's default element. The buffer is cleared. | ||
| /// leaves in place, or the container's default element. The buffer's contents are left in an | ||
| /// undefined state, specifically the caller cannot rely on this function clearing the buffer. | ||
| #[inline] | ||
| pub fn push_at<P: Push<Message<T, C>>>(buffer: &mut C, time: T, pusher: &mut P) { | ||
|
|
||
|
|
@@ -51,7 +52,6 @@ impl<T, C: Container> Message<T, C> { | |
|
|
||
| if let Some(message) = bundle { | ||
| *buffer = message.data; | ||
| buffer.clear(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line prompted me to think I don't actually understand what is going on. It seems like a pretty substantial change to some contracts, and it's worth going through the reason for it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Backed out all the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. Elsewhere (e.g. |
||
| } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A thing I want to discuss, but am not sure I'll find a better place to comment:
My guess is we'd eventually like to close in on a representation that presents its progress tracking information as a
Map<T, usize>, allowing multiple counts for multiple times. This might be in that direction, I think it is, but also I'm trying to think through how this ends up looking and whether there are helpful steps to take as we go.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. We currently have the separation into
Messageand the data it's transferring, and theContainertrait in the past was just the data, mostly for historic reasons. It might make sense to make messages the interface to sending data, which would give more control about the associated timestamps to the users.