mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-11 22:21:58 +00:00
render: seed and mix the node-id cache hash, skip the cell allocation past the pattern limit (#2254)
* render: seed and mix the node-id cache hash, skip the cell allocation past the pattern limit * render: use hash_one in the bucket test
This commit is contained in:
@@ -91,3 +91,11 @@ fn kept_splat_answers_are_the_answers_worked_out_afresh() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ids_chosen_to_differ_only_in_high_bits_still_spread_over_buckets() {
|
||||
use std::hash::BuildHasher;
|
||||
let state = crate::draw::IdState::default();
|
||||
let buckets: std::collections::HashSet<u64> = (0..4096u64).map(|k| state.hash_one(NodeId(k << 40)) & 0xfff).collect();
|
||||
assert!(buckets.len() > 2000, "{}", buckets.len());
|
||||
}
|
||||
|
||||
@@ -69,16 +69,37 @@ impl<'a> Bounds<'a> {
|
||||
}
|
||||
|
||||
/// A hash map keyed by node ids, which need no SipHash: they are counters (or a peer's range of
|
||||
/// them), and SipHash took a good part of a render.
|
||||
type IdMap<K, V> = HashMap<K, V, std::hash::BuildHasherDefault<IdHash>>;
|
||||
/// them), and SipHash took a good part of a render. A file can choose its ids, so each map is
|
||||
/// seeded at random and the hash mixes its high bits down (ids differing only up high would
|
||||
/// otherwise share a bucket).
|
||||
type IdMap<K, V> = HashMap<K, V, IdState>;
|
||||
|
||||
/// Builds [`IdHash`]es from a per-map random seed.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct IdState(u64);
|
||||
|
||||
impl Default for IdState {
|
||||
fn default() -> Self {
|
||||
use std::hash::BuildHasher;
|
||||
IdState(std::collections::hash_map::RandomState::new().hash_one(0u8))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::BuildHasher for IdState {
|
||||
type Hasher = IdHash;
|
||||
fn build_hasher(&self) -> IdHash {
|
||||
IdHash(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mixes each word in with a multiply (Fibonacci hashing), so ids that differ in low bits spread.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct IdHash(u64);
|
||||
|
||||
impl Hasher for IdHash {
|
||||
fn finish(&self) -> u64 {
|
||||
self.0
|
||||
// The multiply's low bits depend only on the input's low bits: mix the high ones down.
|
||||
let h = (self.0 ^ (self.0 >> 32)).wrapping_mul(0xFF51_AFD7_ED55_8CCD);
|
||||
h ^ (h >> 29)
|
||||
}
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
for b in bytes {
|
||||
|
||||
@@ -39,6 +39,10 @@ pub fn nested<T>(f: impl FnOnce() -> Option<T>) -> Option<T> {
|
||||
/// the pixmap, the cell and the pixmap's pixels per unit on each axis (whole pixels per cell, so
|
||||
/// the repeat lines up exactly).
|
||||
pub fn cell_pixmap(doc: &Document, pt: &PatternPaint, w: f64, h: f64, scale: f64, time: f64) -> Option<(Pixmap, PatternCell, (f64, f64))> {
|
||||
// Past the nesting limit nothing is drawn, so don't allocate a cell first.
|
||||
if DEPTH.with(Cell::get) >= MAX_DEPTH {
|
||||
return None;
|
||||
}
|
||||
let src = doc.node(NodeId(pt.source)).filter(|n| !n.is_page())?;
|
||||
let cell = pt.cell(src.width, src.height, w, h)?;
|
||||
let scale = if scale.is_finite() && scale > 0.0 { scale } else { 1.0 };
|
||||
|
||||
Reference in New Issue
Block a user