Commit b748cc16 authored by Michael Müller's avatar Michael Müller Committed by Hero Bird

Implement Heap storage primitive (#206)

* Fix typos

* Fix linter errors in cmd cli

The CI complains.

* Add Heap collection

* Apply CI style recommendations

* Remove `HeapType` param from constructor

Instead of passing `HeapType::Min`/`HeapType::Max`
to the constructor one should instead explictly
implement `Ord` on the generic type which is
stored in the heap.

This is the way the Rust API works as well.

* Apply CI style recommendations

* Improve iterator test/docs

* Improve doc comments

* Fix typo

* Reduce storage fetches via wrapper around SyncChunk

* Rename Heap to BinaryHeap

* Make `AccessWrapper` independent

* Simplify ternary heap to binary heap

* Rename AccessWrapper to DuplexSyncChunk

* Remove superfluous Flush bound

* Simplify code by removing BinaryHeapHeader

* Removed unnecessary Copy + Clone bounds

* Satisfy rustfmt

* Add clarifying comment for testing struct

* Replace expected arg with predicate condition

* Substitute mem::replace with Option API functions

* Improve grouping in DuplexSyncChunk

* Improve comment

* Satisfy rustfmt

* Derive Ord/PartialOrd implementation

* Remove unnecessary trait bounds

* Use compound trait bound

* Fix typo

* Add metadata for DuplexSyncChunk

* Satisfy rustfmt
parent d5863ac4
Pipeline #55545 failed with stages
in 20 seconds
......@@ -131,7 +131,7 @@ impl<T> SyncChunk<T> {
self.cache.update_mut(n, None);
}
/// Returns the unterlying key to the cells.
/// Returns the underlying key to the cells.
///
/// # Note
///
......
......@@ -113,7 +113,7 @@ impl<T> scale::Decode for TypedChunk<T> {
}
impl<T> TypedChunk<T> {
/// Returns the unterlying key to the cells.
/// Returns the underlying key to the cells.
///
/// # Note
///
......
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of ink!.
//
// ink! is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ink! is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
//! Provides a wrapper around `SyncChunk` which stores a defined amount
//! of values in one cell (instead of the `SyncChunk` behavior of storing
//! one value per cell). The intention is to reduce expensive fetch
//! operations from storage.
//!
//! **NOTE** This wrapper is geared explicitly towards a binary tree
//! structure -- the value which is stored at index `0` (the root) will
//! always be stored in its own group with no other values in it. The
//! intention is to be able to store child nodes paired together
//! in a group, since for query operations you have to access both
//! elements anyways. This allows to skip one expensive read for every
//! accessed pair.
//!
//! For example, for `COUNT = 2` the first group (at index `0`) will
//! contain `[Some(root), None]`. The subsequent group at index `1`
//! will contain `[Some(value from index 1), Some(value from index 2)]`.
//! The getters and setters exposed by this module take care of mapping
//! to the correct group index.
use crate::storage::{
chunk::SyncChunk,
Flush,
};
#[cfg(feature = "ink-generate-abi")]
use ink_abi::{
HasLayout,
LayoutField,
LayoutStruct,
StorageLayout,
};
use scale::{
Codec,
Decode,
Encode,
};
#[cfg(feature = "ink-generate-abi")]
use type_metadata::Metadata;
// Number of values stored in each entry of the `SyncChunk`.
// Note that the first group (at index `0`) will only ever
// contain one value.
const COUNT: u32 = 2;
#[derive(Debug, Encode, Decode)]
#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
pub struct Group<T>([Option<T>; COUNT as usize]);
#[derive(Debug, Encode, Decode)]
#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
pub struct DuplexSyncChunk<T>(SyncChunk<Group<T>>);
impl<T> Flush for DuplexSyncChunk<T>
where
SyncChunk<Group<T>>: Flush,
{
fn flush(&mut self) {
self.0.flush();
}
}
#[cfg(feature = "ink-generate-abi")]
impl<T> HasLayout for DuplexSyncChunk<T>
where
T: Metadata + 'static,
{
fn layout(&self) -> StorageLayout {
LayoutStruct::new(
Self::meta_type(),
vec![LayoutField::of("sync_chunk", &self.0)],
).into()
}
}
impl<T> DuplexSyncChunk<T>
where
T: Codec,
{
pub fn new(chunk: SyncChunk<Group<T>>) -> DuplexSyncChunk<T> {
DuplexSyncChunk(chunk)
}
/// Returns the value of the `n`-th cell if any.
pub fn get(&self, n: u32) -> Option<&T> {
let group = get_group_index(n);
let in_group = get_ingroup_index(n);
match self.0.get(group).map(|g| g.0[in_group].as_ref()) {
None => None,
Some(v) => v,
}
}
/// Returns the value of the `n`-th cell if any.
pub fn get_group(&self, group: u32) -> Option<&Group<T>> {
self.0.get(group)
}
/// Returns the value of the `n`-th cell if any.
pub fn get_mut(&mut self, n: u32) -> Option<&mut T> {
let group = get_group_index(n);
match self.0.get_mut(group).map(|g| {
let in_group = get_ingroup_index(n);
g.0[in_group].as_mut()
}) {
None => None,
Some(v) => v,
}
}
/// Takes the value of the `n`-th cell if any.
pub fn take(&mut self, n: u32) -> Option<T> {
let group = get_group_index(n);
match self.0.take(group) {
None => None,
Some(existing_group) => {
let mut existing_group = existing_group.0;
let in_group = get_ingroup_index(n);
let taken = existing_group[in_group].take();
let _ = self.0.put(group, Group(existing_group));
taken
}
}
}
/// Replaces the value of the `n`-th cell and returns its old value if any.
pub fn put(&mut self, n: u32, new_val: T) -> Option<T> {
let group = get_group_index(n);
let in_group = get_ingroup_index(n);
match self.0.get_mut(group) {
None => {
let mut new_group: [Option<T>; COUNT as usize] = Default::default();
new_group[in_group] = Some(new_val);
let _ = self.0.put(group, Group(new_group));
None
}
Some(existing_group) => existing_group.0[in_group].replace(new_val),
}
}
}
/// Returns the group index of the `n`-th cell.
fn get_group_index(n: u32) -> u32 {
match n {
0 => 0,
_ => {
// the first group only ever contains a single element:
// the root node (e.g. for `COUNT = 2`, `[Some(root), None]`).
// so when calculating indices we need to account for the
// items which have been left empty in the first group.
let padding = COUNT - 1;
(n + padding) / COUNT
}
}
}
/// Returns the in-group index of the `n`-th cell.
/// This refers to the index which the cell has within a group.
///
/// For example, for `COUNT = 2` the cell `3` is found at in-group
/// index `0` (within the group at index `2`).
fn get_ingroup_index(n: u32) -> usize {
let group = get_group_index(n);
match (group, n) {
(0, 0) => 0,
(0, _) => panic!("first group contains only root node"),
(_, _) => ((n - 1) % COUNT) as usize,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_get_group_index() {
assert_eq!(get_group_index(0), 0);
assert_eq!(get_group_index(1), 1);
assert_eq!(get_group_index(2), 1);
assert_eq!(get_group_index(3), 2);
assert_eq!(get_group_index(4), 2);
assert_eq!(get_group_index(5), 3);
assert_eq!(get_group_index(6), 3);
assert_eq!(get_group_index(7), 4);
}
#[test]
fn should_get_ingroup_index() {
assert_eq!(get_ingroup_index(0), 0);
assert_eq!(get_ingroup_index(1), 0);
assert_eq!(get_ingroup_index(2), 1);
assert_eq!(get_ingroup_index(3), 0);
assert_eq!(get_ingroup_index(4), 1);
assert_eq!(get_ingroup_index(5), 0);
assert_eq!(get_ingroup_index(6), 1);
}
}
This diff is collapsed.
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of ink!.
//
// ink! is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ink! is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
//! A binary heap collection.
//! The heap depends on `Ord` and is a max-heap by default. In order to
//! make it a min-heap implement the `Ord` trait explicitly on the type
//! which is stored in the heap.
//!
//! Provides `O(log(n))` push and pop operations.
#[cfg(all(test, feature = "test-env"))]
mod tests;
mod duplex_sync_chunk;
mod impls;
pub use self::impls::{
BinaryHeap,
Iter,
Values,
};
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of ink!.
//
// ink! is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ink! is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
use crate::{
storage::{
alloc::{
AllocateUsing,
BumpAlloc,
Initialize,
},
BinaryHeap,
Key,
},
test_utils::run_test,
};
use core::{
cmp::Ord,
fmt::Debug,
};
use scale::{
Codec,
Decode,
Encode,
};
fn empty_heap() -> BinaryHeap<i32> {
unsafe {
let mut alloc = BumpAlloc::from_raw_parts(Key([0x0; 32]));
BinaryHeap::allocate_using(&mut alloc).initialize_into(())
}
}
fn filled_heap() -> BinaryHeap<i32> {
let mut heap = empty_heap();
heap.push(42);
heap.push(5);
heap.push(1337);
heap.push(77);
assert_eq!(heap.len(), 4);
heap
}
/// Pushes all element from `vec` onto the heap, in the order in which they
/// are supplied in the vector.
///
/// Subsequently all elements are popped from the vec and for the retrieved
/// elements it is asserted that they are in the exact same order as the ones
/// in `expected`. The `expected` vec must contain all elements which are
/// returned, as the function finally checks that there are no more elements
/// left in the heap.
fn assert_push_equals_sorted_pop<T: Ord + Codec + Debug>(
heap: &mut BinaryHeap<T>,
vec: Vec<T>,
) {
vec.into_iter().for_each(|i| heap.push(i));
let mut prior = None;
while let Some(val) = heap.pop() {
prior.map(|p| assert!(val <= p)); // it's a max heap
prior = Some(val);
}
assert_eq!(heap.pop(), None);
assert_eq!(heap.len(), 0);
}
#[test]
fn new_unchecked() {
run_test(|| {
// given
let heap = empty_heap();
// then
assert_eq!(heap.len(), 0);
assert!(heap.is_empty());
assert_eq!(heap.iter().next(), None);
})
}
#[test]
fn push_on_empty_heap() {
run_test(|| {
// given
let mut heap = empty_heap();
assert_eq!(heap.pop(), None);
// when
heap.push(42);
// then
assert_eq!(heap.len(), 1);
assert_eq!(heap.pop(), Some(42));
})
}
#[test]
fn push_duplicates_max() {
run_test(|| {
// given
let mut heap = empty_heap();
// when
heap.push(10);
heap.push(20);
heap.push(10);
heap.push(20);
// then
assert_eq!(heap.pop(), Some(20));
assert_eq!(heap.pop(), Some(20));
assert_eq!(heap.pop(), Some(10));
assert_eq!(heap.pop(), Some(10));
})
}
#[test]
fn peek() {
run_test(|| {
// given
let mut heap = empty_heap();
assert_eq!(heap.peek(), None);
// when
heap.push(42);
// then
assert_eq!(heap.peek(), Some(&42));
})
}
#[test]
fn peek_mut() {
run_test(|| {
// given
let mut heap = empty_heap();
heap.push(42);
// when
let val = heap.peek_mut().unwrap();
assert_eq!(val, &42);
*val = 1337;
// then
assert_eq!(heap.peek(), Some(&1337));
})
}
#[test]
fn pop_empty_and_refill() {
run_test(|| {
// given
let mut heap = filled_heap();
for _ in 0..heap.len() {
let _ = heap.pop();
}
assert_eq!(heap.len(), 0);
// when
heap.push(123);
// then
assert_eq!(heap.pop(), Some(123));
assert_eq!(heap.len(), 0);
})
}
#[test]
fn take_empty() {
run_test(|| {
// given
let mut heap = empty_heap();
// then
assert_eq!(heap.pop(), None);
assert_eq!(heap.peek(), None);
assert_eq!(heap.peek_mut(), None);
})
}
#[test]
fn push_negative_positive_range_min() {
run_test(|| {
// given
let mut heap = empty_heap();
// when
heap.push(-1);
heap.push(0);
heap.push(1);
// then
assert_eq!(heap.len(), 3);
assert_eq!(heap.pop(), Some(1));
assert_eq!(heap.pop(), Some(0));
assert_eq!(heap.pop(), Some(-1));
})
}
#[test]
fn push_negative_positive_range_max() {
run_test(|| {
// given
let mut heap = empty_heap();
// when
heap.push(-1);
heap.push(0);
heap.push(1);
// then
assert_eq!(heap.len(), 3);
assert_eq!(heap.pop(), Some(1));
assert_eq!(heap.pop(), Some(0));
assert_eq!(heap.pop(), Some(-1));
})
}
#[test]
fn iter() {
run_test(|| {
// given
let heap = filled_heap();
// when
let mut iter = heap.iter();
// then
// order can be arbitrary
assert_eq!(iter.next(), Some((0, &1337)));
assert_eq!(iter.next(), Some((1, &77)));
assert_eq!(iter.next(), Some((2, &42)));
assert_eq!(iter.next(), Some((3, &5)));
assert_eq!(iter.next(), None);
})
}
#[test]
fn iter_back() {
run_test(|| {
// given
let heap = filled_heap();
// when
let mut iter = heap.iter();
// then
assert_eq!(iter.next_back(), Some((3, &5)));
assert_eq!(iter.next_back(), Some((2, &42)));
assert_eq!(iter.next_back(), Some((1, &77)));
assert_eq!(iter.next_back(), Some((0, &1337)));
assert_eq!(iter.next_back(), None);
})
}
#[test]
fn iter_size_hint() {
run_test(|| {
// given
let heap = filled_heap();
// when
let mut iter = heap.iter();
assert_eq!(iter.size_hint(), (4, Some(4)));
// then
iter.next();
assert_eq!(iter.size_hint(), (3, Some(3)));
})
}
#[test]
fn unordered_push_results_in_ordered_pop() {
run_test(|| {
let mut heap = empty_heap();
let vec = vec![5, 42, 1337, 77, -1, 0, 9999, 3, 65, 90, 1000000, -32];
assert_push_equals_sorted_pop(&mut heap, vec);
})
}
#[test]
fn max_heap_with_multiple_levels() {
run_test(|| {
let mut heap = empty_heap();
let vec = vec![100, 10, 20, 30, 7, 8, 9, 17, 18, 29, 27, 28, 30];
assert_push_equals_sorted_pop(&mut heap, vec);
})
}
/// A simple wrapper struct which is stored in the heap
/// for testing purposes (mostly to verify that custom
/// implemented `Ord` and `PartialOrd` are respected).
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Encode, Decode)]
struct V(u32);
#[test]
fn min_heap_with_multiple_levels() {
run_test(|| {
let mut heap: BinaryHeap<V> = unsafe {
let mut alloc = BumpAlloc::from_raw_parts(Key([0x0; 32]));
BinaryHeap::allocate_using(&mut alloc).initialize_into(())
};
let vec = vec![
V(100),
V(10),
V(20),
V(30),
V(7),
V(8),
V(9),
V(17),
V(18),
V(29),
V(27),
V(28),
V(30),
];
assert_push_equals_sorted_pop(&mut heap, vec);
})
}
......@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
pub mod binary_heap;
pub mod bitvec;
pub mod hash_map;
pub mod stash;
......
......@@ -62,7 +62,7 @@ use type_metadata::Metadata;
#[derive(Debug)]
#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
pub struct Stash<T> {
/// Stores densly packed general stash information.
/// Stores densely packed general stash information.
header: storage::Value<StashHeader>,
/// The entries of the stash.
entries: SyncChunk<Entry<T>>,
......@@ -74,7 +74,7 @@ pub struct Stash<T> {
///
/// Separation of these fields into a sub structure has been made
/// for performance reasons so that they all reside in the same
/// storage entiry. This allows implementations to perform less reads
/// storage entity. This allows implementations to perform less reads
/// and writes to the underlying contract storage.
#[derive(Debug, Encode, Decode)]
#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
......@@ -336,7 +336,7 @@ impl<T> Stash<T> {
Values::new(self)
}
/// Returns the unterlying key to the cells.
/// Returns the underlying key to the cells.
///
/// # Note
///
......
......@@ -87,6 +87,10 @@ use self::non_clone::NonCloneMarker;
pub use self::{
collections::{
binary_heap::{
self,
BinaryHeap,
},
bitvec::{
self,
BitVec,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment