steel_worldgen/noise/
corner_column_store.rs1use rustc_hash::{FxBuildHasher, FxHashMap};
7use std::hash::BuildHasher;
8use steel_utils::locks::SyncMutex;
9
10const SHARD_COUNT: usize = 64;
11const MAX_STORED_VALUES: usize = 8 * 1024 * 1024;
13
14struct Column {
15 values: Box<[f32]>,
16 remaining: u8,
18}
19
20#[derive(Default)]
21struct Shard {
22 columns: FxHashMap<(i32, i32), Column>,
23 stored_values: usize,
24}
25
26pub struct CornerColumnStore {
28 shards: Box<[SyncMutex<Shard>]>,
29}
30
31impl Default for CornerColumnStore {
32 fn default() -> Self {
33 Self {
34 shards: (0..SHARD_COUNT)
35 .map(|_| SyncMutex::new(Shard::default()))
36 .collect(),
37 }
38 }
39}
40
41impl CornerColumnStore {
42 fn shard(&self, key: (i32, i32)) -> &SyncMutex<Shard> {
43 &self.shards[FxBuildHasher.hash_one(key) as usize % SHARD_COUNT]
44 }
45
46 pub fn consume(&self, key: (i32, i32), read: impl FnOnce(&[f32])) -> bool {
48 let mut shard = self.shard(key).lock();
49 let Some(column) = shard.columns.get_mut(&key) else {
50 return false;
51 };
52
53 read(&column.values);
54 column.remaining -= 1;
55 if column.remaining == 0 {
56 shard.remove(key);
57 }
58
59 true
60 }
61
62 pub fn produce(&self, key: (i32, i32), users: u8, values: impl FnOnce() -> Box<[f32]>) {
64 debug_assert!(users > 1, "only shared columns are stored");
65 let mut shard = self.shard(key).lock();
66 if let Some(column) = shard.columns.get_mut(&key) {
67 column.remaining -= 1;
68 if column.remaining == 0 {
69 shard.remove(key);
70 }
71
72 return;
73 }
74
75 let values = values();
76 if shard.stored_values + values.len() > MAX_STORED_VALUES / SHARD_COUNT {
77 shard.columns.clear();
78 shard.stored_values = 0;
79 }
80
81 shard.stored_values += values.len();
82 shard.columns.insert(
83 key,
84 Column {
85 values,
86 remaining: users - 1,
87 },
88 );
89 }
90}
91
92impl Shard {
93 fn remove(&mut self, key: (i32, i32)) {
94 if let Some(column) = self.columns.remove(&key) {
95 self.stored_values -= column.values.len();
96 }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::CornerColumnStore;
103
104 fn read(store: &CornerColumnStore, key: (i32, i32)) -> Option<Vec<f32>> {
105 let mut out = None;
106 store.consume(key, |values| out = Some(values.to_vec()));
107 out
108 }
109
110 #[test]
111 fn last_user_removes_the_column() {
112 let store = CornerColumnStore::default();
113 assert_eq!(read(&store, (0, 0)), None);
114
115 store.produce((0, 0), 2, || vec![1.0, 2.0].into());
116 assert_eq!(read(&store, (0, 0)), Some(vec![1.0, 2.0]));
117 assert_eq!(read(&store, (0, 0)), None);
118
119 store.produce((4, 4), 4, || vec![3.0].into());
121 for _ in 0..3 {
122 assert_eq!(read(&store, (4, 4)), Some(vec![3.0]));
123 }
124 assert_eq!(read(&store, (4, 4)), None);
125 }
126
127 #[test]
128 fn concurrent_producers_count_as_uses() {
129 let store = CornerColumnStore::default();
130 store.produce((0, 8), 2, || vec![1.0].into());
132 store.produce((0, 8), 2, || unreachable!("already stored"));
133 assert_eq!(read(&store, (0, 8)), None);
134 }
135}