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);
}
}
// 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 super::duplex_sync_chunk::DuplexSyncChunk;
use crate::storage::{
self,
alloc::{
Allocate,
AllocateUsing,
Initialize,
},
chunk::SyncChunk,
Flush,
};
use core::cmp::{
Ord,
Ordering,
};
#[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;
/// We implement a binary tree.
pub const CHILDREN: u32 = 2;
/// 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.
#[derive(Debug)]
#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
pub struct BinaryHeap<T> {
/// The number of nodes stored in the heap.
len: storage::Value<u32>,
/// The nodes of the heap.
entries: DuplexSyncChunk<T>,
}
/// Iterator over the values of a heap.
#[derive(Debug)]
pub struct Values<'a, T> {
/// The underlying iterator.
iter: Iter<'a, T>,
}
impl<'a, T> Values<'a, T> {
/// Creates a new iterator for the given storage heap.
pub(crate) fn new(heap: &'a BinaryHeap<T>) -> Self
where
T: scale::Codec + Ord,
{
Self { iter: heap.iter() }
}
}
impl<T> Flush for BinaryHeap<T>
where
T: Encode + Flush,
DuplexSyncChunk<T>: Flush,
{
fn flush(&mut self) {
self.len.flush();
self.entries.flush();
}
}
#[cfg(feature = "ink-generate-abi")]
impl<T> HasLayout for BinaryHeap<T>
where
T: Metadata + 'static,
{
fn layout(&self) -> StorageLayout {
LayoutStruct::new(
Self::meta_type(),
vec![
LayoutField::of("len", &self.len),
LayoutField::of("entries", &self.entries),
],
)
.into()
}
}
impl<'a, T> Iterator for Values<'a, T>
where
T: Codec + Ord,
{
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(_index, value)| value)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<'a, T> ExactSizeIterator for Values<'a, T> where T: Codec + Ord {}
impl<'a, T> DoubleEndedIterator for Values<'a, T>
where
T: Codec + Ord,
{
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back().map(|(_index, value)| value)
}
}
/// Iterator over the elements of a heap. The iteration is not
/// guaranteed to be ordered, it is arbitrary!
#[derive(Debug)]
pub struct Iter<'a, T> {
/// The heap that is iterated over.
heap: &'a BinaryHeap<T>,
/// The index of the current start node of the iteration.
begin: u32,
/// The index of the current end node of the iteration.
end: u32,
/// The amount of already yielded nodes.
///
/// Required to offer an exact `size_hint` implementation.
/// Also can be used to exit iteration as early as possible.
yielded: u32,
}
impl<'a, T> Iter<'a, T> {
/// Creates a new iterator for the given storage heap.
pub(crate) fn new(heap: &'a BinaryHeap<T>) -> Self
where
T: Codec + Ord,
{
Self {
heap,
begin: 0,
end: heap.len(),
yielded: 0,
}
}
}
impl<'a, T> Iterator for Iter<'a, T>
where
T: Codec + Ord,
{
type Item = (u32, &'a T);
fn next(&mut self) -> Option<Self::Item> {
debug_assert!(self.begin <= self.end);
if self.yielded == self.heap.len() {
return None
}
while self.begin < self.end {
let cur = self.begin;
self.begin += 1;
if let Some(elem) = self.heap.get(cur) {
self.yielded += 1;
return Some((cur, elem))
}
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.heap.len() - self.yielded) as usize;
(remaining, Some(remaining))
}
}
impl<'a, T> ExactSizeIterator for Iter<'a, T> where T: Codec + Ord {}
impl<'a, T> DoubleEndedIterator for Iter<'a, T>
where
T: Codec + Ord,
{
fn next_back(&mut self) -> Option<Self::Item> {
debug_assert!(self.begin <= self.end);
if self.yielded == self.heap.len() {
return None
}
while self.begin < self.end {
self.end -= 1;
if let Some(elem) = self.heap.get(self.end) {
self.yielded += 1;
return Some((self.end, elem))
}
}
None
}
}
impl<T> Encode for BinaryHeap<T> {
fn encode_to<W: scale::Output>(&self, dest: &mut W) {
self.len.encode_to(dest);
self.entries.encode_to(dest);
}
}
impl<T> Decode for BinaryHeap<T>
where
T: Codec,
{
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
let len = storage::Value::decode(input)?;
let entries = SyncChunk::decode(input)?;
Ok(Self {
len,
entries: DuplexSyncChunk::new(entries),
})
}
}
impl<T> AllocateUsing for BinaryHeap<T>
where
T: Codec,
{
unsafe fn allocate_using<A>(alloc: &mut A) -> Self
where
A: Allocate,
{
Self {
len: storage::Value::allocate_using(alloc),
entries: DuplexSyncChunk::new(SyncChunk::allocate_using(alloc)),
}
}
}
impl<T> Initialize for BinaryHeap<T> {
type Args = ();
fn default_value() -> Option<Self::Args> {
Some(())
}
fn initialize(&mut self, _: Self::Args) {
self.len.set(0);
}
}
impl<T> BinaryHeap<T>
where
T: Codec + Ord,
{
/// Returns the element stored at index `n` if any.
pub fn len(&self) -> u32 {
*self.len.get()
}
/// Returns `true` if the heap is empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the first node if not empty.
pub fn peek(&self) -> Option<&T> {
self.entries.get(0)
}
/// Mutates the first node if not empty and returns a reference to the result.
pub fn peek_mut(&mut self) -> Option<&mut T> {
self.entries.get_mut(0)
}
/// If the heap is not empty the first node is returned and removed.
///
/// Complexity is `O(log(n))`.
pub fn pop(&mut self) -> Option<T> {
let len = self.len();
if len == 0 {
return None
}
let tmp = Some(self.entries.take(0).expect("failed fetching root"));
if len == 1 {
self.len.set(len - 1);
return tmp
}
self.relocate(len - 1, 0);
self.len.set(len - 1);
self.repair_top();
tmp
}
/// Move the top of the heap to its correct place within the heap, so that
/// sort order is maintained.
fn repair_top(&mut self) {
let mut top_index = 0;
let top_value = self
.entries
.take(top_index)
.expect("failed taking top element from heap");
let mut succ_index = self.find_successor(top_index);
while succ_index < self.len() && {
let succ_value = self
.entries
.get(succ_index)
.expect("failed retrieving successor");
top_value < *succ_value
} {
self.relocate(succ_index, top_index);
top_index = succ_index;
succ_index = self.find_successor(succ_index);
}
let _ = self.entries.put(top_index, top_value);
}
/// Returns the index of the child node with the largest value.
///
/// The `index` parameter refers to the parent node.
fn find_successor(&mut self, index: u32) -> u32 {
let left_index = index * CHILDREN + 1;
let right_index = index * CHILDREN + 2;
if right_index >= self.len() {
return left_index
}
let left = self
.entries
.get(left_index)
.expect("failed getting left value");
let right = self
.entries
.get(right_index)
.expect("failed getting right value");
match left.cmp(right) {
Ordering::Less => right_index,
Ordering::Equal => right_index,
Ordering::Greater => left_index,
}
}
/// Pushes an item onto the heap.
///
/// Panics in case the heap already contains `u32::max` nodes.
/// Complexity is `O(log(n))`.
pub fn push(&mut self, val: T) {
let len = self.len();
if len == u32::max_value() {
panic!(
"[ink_core::Heap::push] Error: \
cannot push more elements than `u32::Max`"
)
}
if len == 0 {
let _ = self.entries.put(0, val);
self.len.set(len + 1);
return
}