Enable compilation of crate for no_std again.
Several regressions caused the no_std support to bitrot. This adds back
support for no_std environments.
As part of this change, I had to revamp the Id re-use functionality. The
good news? Creating new IDs is now atomically lock-free. The bad news?
Freeing IDs uses a spin lock. Performance could be greatly improved by
freeing more IDs at the same time, as the signature would already suggest;
however it seems like no code is taking advantage of that right now.
0582914f5abfde3aaaafa507a3806f21f0e5f81a · by David Flemström · 2019-09-14T14:13:26Z (7 years ago) · parent 03b0a161d · browse the tree at this commit
Semantic delta · 17
src/lib.rs
signature changed enum Error pub
diff --git a/Cargo.toml b/Cargo.toml
index 3bb8e14b3..c6f4599a8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,8 +16,9 @@ circle-ci = { repository = "vislyhq/stretch", branch = "master" }
maintenance = { status = "experimental" }
[dependencies]
-libm = "0.1.2"
+hashbrown = "0.6.0"
lazy_static = "1.3"
+spin = "0.5.2"
[dependencies.serde]
version = "1.0.99"
diff --git a/src/algo.rs b/src/algo.rs
index 18bb933d6..d4bde363f 100644
--- a/src/algo.rs
+++ b/src/algo.rs
@@ -1,7 +1,5 @@
#[cfg(not(feature = "std"))]
-use alloc::{vec, vec::Vec};
-#[cfg(not(feature = "std"))]
-use libm::F32Ext;
+use alloc::{boxed::Box, vec, vec::Vec};
use core::any::Any;
use core::f32;
@@ -59,7 +57,7 @@ struct FlexLine {
}
impl Forest {
- pub(crate) fn compute(&mut self, root: NodeId, size: Size<Number>) -> Result<(), Box<Any>> {
+ pub(crate) fn compute(&mut self, root: NodeId, size: Size<Number>) -> Result<(), Box<dyn Any>> {
let style = self.nodes[root].style;
let has_root_min_max = style.min_size.width.is_defined()
|| style.min_size.height.is_defined()
@@ -132,7 +130,7 @@ impl Forest {
node_size: Size<Number>,
parent_size: Size<Number>,
perform_layout: bool,
- ) -> Result<ComputeResult, Box<Any>> {
+ ) -> Result<ComputeResult, Box<dyn Any>> {
self.nodes[node].is_dirty = false;
// First we check if we have a result for the given input
@@ -276,7 +274,7 @@ impl Forest {
// TODO - this does not follow spec. See commented out code below
// 3. Determine the flex base size and hypothetical main size of each item:
- flex_items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> {
+ flex_items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
let child_style = self.nodes[child.node].style;
// A. If the item has a definite used flex basis, that’s the flex base size.
@@ -363,7 +361,7 @@ impl Forest {
// The hypothetical main size is the item’s flex base size clamped according to its
// used min and max main sizes (and flooring the content box size at zero).
- flex_items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> {
+ flex_items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
child.inner_flex_basis = child.flex_basis - child.padding.main(dir) - child.border.main(dir);
// TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
@@ -440,7 +438,7 @@ impl Forest {
//
// 9.7. Resolving Flexible Lengths
- flex_lines.iter_mut().try_for_each(|line| -> Result<(), Box<Any>> {
+ flex_lines.iter_mut().try_for_each(|line| -> Result<(), Box<dyn Any>> {
// 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
// items on the line. If the sum is less than the flex container’s inner main size,
// use the flex grow factor for the rest of this algorithm; otherwise, use the
@@ -457,7 +455,7 @@ impl Forest {
// - If using the flex shrink factor: any item that has a flex base size
// smaller than its hypothetical main size
- line.items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> {
+ line.items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
// TODO - This is not found by reading the spec. Maybe this can be done in some other place
// instead. This was found by trail and error fixing tests to align with webkit output.
if node_inner_size.main(dir).is_undefined() && is_row {
@@ -615,7 +613,7 @@ impl Forest {
// item’s target main size was made smaller by this, it’s a max violation.
// If the item’s target main size was made larger by this, it’s a min violation.
- let total_violation = unfrozen.iter_mut().try_fold(0.0, |acc, child| -> Result<f32, Box<Any>> {
+ let total_violation = unfrozen.iter_mut().try_fold(0.0, |acc, child| -> Result<f32, Box<dyn Any>> {
// TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
// The following logic was developed not from the spec but by trail and error looking into how
// webkit handled various scenarios. Can probably be solved better by passing in
@@ -692,7 +690,7 @@ impl Forest {
// used main size and the available space, treating auto as fit-content.
flex_lines.iter_mut().try_for_each(|line| {
- line.items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> {
+ line.items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
let child_cross =
child.size.cross(dir).maybe_max(child.min_size.cross(dir)).maybe_min(child.max_size.cross(dir));
@@ -738,7 +736,7 @@ impl Forest {
if has_baseline_child {
flex_lines.iter_mut().try_for_each(|line| {
- line.items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> {
+ line.items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
let result = self.compute_internal(
child.node,
Size {
@@ -1149,12 +1147,12 @@ impl Forest {
let mut lines: Vec<Vec<result::Layout>> = vec![];
let mut total_offset_cross = padding_border.cross_start(dir);
- let layout_line = |line: &mut FlexLine| -> Result<(), Box<Any>> {
+ let layout_line = |line: &mut FlexLine| -> Result<(), Box<dyn Any>> {
let mut children: Vec<result::Layout> = vec![];
let mut total_offset_main = padding_border.main_start(dir);
let line_offset_cross = line.offset_cross;
- let layout_item = |child: &mut FlexItem| -> Result<(), Box<Any>> {
+ let layout_item = |child: &mut FlexItem| -> Result<(), Box<dyn Any>> {
let result = self.compute_internal(
child.node,
child.target_size.map(|s| s.to_number()),
diff --git a/src/forest.rs b/src/forest.rs
index cce8bd842..06422c805 100644
--- a/src/forest.rs
+++ b/src/forest.rs
@@ -2,6 +2,9 @@
//!
//! Backing datastructure for `Stretch` structs.
+#[cfg(not(feature = "std"))]
+use alloc::vec::Vec;
+
use crate::geometry::Size;
use crate::id::NodeId;
use crate::node::MeasureFunc;
diff --git a/src/id.rs b/src/id.rs
index 0b76c8934..db1085913 100644
--- a/src/id.rs
+++ b/src/id.rs
@@ -1,6 +1,9 @@
//! Identifier for a Node
//!
//!
+#[cfg(not(feature = "std"))]
+use alloc::vec::Vec;
+use core::sync::atomic;
/// Internal node id.
pub(crate) type NodeId = usize;
@@ -12,28 +15,110 @@ pub(crate) struct Id {
}
pub(crate) struct Allocator {
- new_id: u32,
- free_ids: Vec<Id>,
+ new_id: atomic::AtomicU32,
+ free_ids: spin::RwLock<IdCache>,
}
impl Allocator {
pub fn new() -> Self {
- Allocator { new_id: 0, free_ids: Vec::new() }
- }
-
- pub fn allocate(&mut self) -> Id {
- // TODO: better balancing
- match self.free_ids.pop() {
- Some(id) => Id { id: id.id, generation: id.generation + 1 },
- None => {
- let id = self.new_id;
- self.new_id += 1;
- Id { id, generation: 0 }
- }
+ Allocator { new_id: atomic::AtomicU32::new(0), free_ids: spin::RwLock::new(IdCache::default()) }
+ }
+
+ pub fn allocate(&self) -> Id {
+ self.free_ids
+ .read()
+ .pop_atomic()
+ .map(|Id { id, generation }| Id { id, generation: generation + 1 })
+ .unwrap_or_else(|| Id {
+ id: atomic_increment(&self.new_id).expect("No entity left to allocate"),
+ generation: 0,
+ })
+ }
+
+ pub fn free(&self, ids: &[Id]) {
+ self.free_ids.write().extend(ids.iter().copied());
+ }
+}
+
+// Code below this line is based on code from the `specs` library, in the `src/world/entity.rs`
+// file, which has the following copyright notice (MIT license):
+
+/*
+Copyright (c) 2017 The Specs Project Developers
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+#[derive(Default, Debug)]
+struct IdCache {
+ cache: Vec<Id>,
+ len: atomic::AtomicUsize,
+}
+
+impl IdCache {
+ fn pop_atomic(&self) -> Option<Id> {
+ atomic_decrement(&self.len).map(|x| self.cache[x - 1])
+ }
+
+ fn maintain(&mut self) {
+ self.cache.truncate(*(self.len.get_mut()));
+ }
+}
+
+impl Extend<Id> for IdCache {
+ fn extend<T: IntoIterator<Item = Id>>(&mut self, iter: T) {
+ self.maintain();
+ self.cache.extend(iter);
+ *self.len.get_mut() = self.cache.len();
+ }
+}
+
+/// Increments `i` atomically without wrapping on overflow.
+/// Resembles a `fetch_add(1, Ordering::Relaxed)` with
+/// checked overflow, returning `None` instead.
+fn atomic_increment(i: &atomic::AtomicU32) -> Option<u32> {
+ let mut prev = i.load(atomic::Ordering::Relaxed);
+ while prev != core::u32::MAX {
+ match i.compare_exchange_weak(prev, prev + 1, atomic::Ordering::Relaxed, atomic::Ordering::Relaxed) {
+ Ok(x) => return Some(x),
+ Err(next_prev) => prev = next_prev,
}
}
+ None
+}
- pub fn free(&mut self, ids: &[Id]) {
- self.free_ids.extend(ids);
+/// Decrements `i` atomically without wrapping on overflow.
+/// Resembles a `fetch_sub(1, Ordering::Relaxed)` with
+/// checked underflow, returning `None` instead.
+fn atomic_decrement(i: &atomic::AtomicUsize) -> Option<usize> {
+ let mut prev = i.load(atomic::Ordering::Relaxed);
+ while prev != 0 {
+ match i.compare_exchange_weak(prev, prev - 1, atomic::Ordering::Relaxed, atomic::Ordering::Relaxed) {
+ Ok(x) => return Some(x),
+ Err(next_prev) => prev = next_prev,
+ }
}
+ None
}
diff --git a/src/lib.rs b/src/lib.rs
index 5ffa0ac94..2e1284112 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,8 +1,10 @@
#![cfg_attr(not(feature = "std"), no_std)]
-#![cfg_attr(not(feature = "std"), feature(alloc))]
#[cfg(not(feature = "std"))]
extern crate alloc;
+#[cfg(not(feature = "std"))]
+use alloc::boxed::Box;
+
#[macro_use]
extern crate lazy_static;
@@ -27,9 +29,10 @@ use core::any::Any;
#[derive(Debug)]
pub enum Error {
InvalidNode(node::Node),
- Measure(Box<Any>),
+ Measure(Box<dyn Any>),
}
+#[cfg(feature = "std")]
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
@@ -39,6 +42,7 @@ impl std::fmt::Display for Error {
}
}
+#[cfg(feature = "std")]
impl std::error::Error for Error {
fn description(&self) -> &str {
match *self {
diff --git a/src/node.rs b/src/node.rs
index 0ab316000..4158f8894 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -1,11 +1,12 @@
#[cfg(not(feature = "std"))]
-use alloc::{vec, vec::Vec};
+use alloc::{boxed::Box, vec::Vec};
+#[cfg(not(feature = "std"))]
+use hashbrown::HashMap;
+#[cfg(feature = "std")]
+use std::collections::HashMap;
use core::any::Any;
-
-use std::collections::HashMap;
-use std::ops::Drop;
-use std::sync::Mutex;
+use core::ops::Drop;
use crate::forest::Forest;
use crate::geometry::Size;
@@ -15,11 +16,11 @@ use crate::result::Layout;
use crate::style::*;
use crate::Error;
-pub type MeasureFunc = Box<Fn(Size<Number>) -> Result<Size<f32>, Box<Any>>>;
+pub type MeasureFunc = Box<dyn Fn(Size<Number>) -> Result<Size<f32>, Box<dyn Any>>>;
lazy_static! {
/// Global stretch instance id allocator.
- static ref INSTANCE_ALLOCATOR: Mutex<id::Allocator> = Mutex::new(id::Allocator::new());
+ static ref INSTANCE_ALLOCATOR: id::Allocator = id::Allocator::new();
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
@@ -45,7 +46,7 @@ impl Stretch {
pub fn with_capacity(capacity: usize) -> Self {
Stretch {
- id: INSTANCE_ALLOCATOR.lock().unwrap().allocate(),
+ id: INSTANCE_ALLOCATOR.allocate(),
nodes: id::Allocator::new(),
nodes_to_ids: HashMap::with_capacity(capacity),
ids_to_nodes: HashMap::with_capacity(capacity),
@@ -167,7 +168,7 @@ impl Stretch {
// TODO: index check
self.forest.parents[child_id].push(node_id);
- let old_child = std::mem::replace(&mut self.forest.children[node_id][index], child_id);
+ let old_child = core::mem::replace(&mut self.forest.children[node_id][index], child_id);
self.forest.parents[old_child].retain(|p| *p != node_id);
self.forest.mark_dirty(node_id);
@@ -221,6 +222,6 @@ impl Stretch {
impl Drop for Stretch {
fn drop(&mut self) {
- INSTANCE_ALLOCATOR.lock().unwrap().free(&[self.id]);
+ INSTANCE_ALLOCATOR.free(&[self.id]);
}
}
diff --git a/src/result.rs b/src/result.rs
index 6b464ade9..7f69e8a5f 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -1,6 +1,3 @@
-#[cfg(not(feature = "std"))]
-use alloc::vec::Vec;
-
use crate::algo::ComputeResult;
use crate::geometry::{Point, Size};
use crate::number::Number;
diff --git a/src/style.rs b/src/style.rs
index 0a30256c3..3d2463cc5 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -1,6 +1,3 @@
-#[cfg(not(feature = "std"))]
-use alloc::boxed::Box;
-
use crate::geometry::{Rect, Size};
use crate::number::Number;