-
Notifications
You must be signed in to change notification settings - Fork 144
Expand file tree
/
Copy pathchunk_lock.rs
More file actions
134 lines (126 loc) · 4.21 KB
/
chunk_lock.rs
File metadata and controls
134 lines (126 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use crate::Chunk;
use anyhow::bail;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
pub type ChunkHandle = Arc<ChunkLock>;
/// A wrapper around a RwLock. Cannot be locked for writing when unloaded.
/// This structure exists so that a chunk can be read from even after being unloaded without accidentaly writing to it.
#[derive(Debug)]
pub struct ChunkLock {
loaded: AtomicBool,
lock: RwLock<Chunk>,
}
impl ChunkLock {
pub fn new(chunk: Chunk, loaded: bool) -> Self {
Self {
loaded: AtomicBool::new(loaded),
lock: RwLock::new(chunk),
}
}
/// Returns whether the chunk is loaded.
pub fn is_loaded(&self) -> bool {
self.loaded.load(Ordering::SeqCst)
}
/// Attempts to set the chunk as unloaded. Returns an error if the chunk is locked as writable.
pub fn set_unloaded(&self) -> anyhow::Result<()> {
if self.loaded.swap(false, Ordering::SeqCst) {
// FIXME: Decide what to do when unloading an unloaded chunk
}
if self.lock.try_read().is_none() {
// Locking fails when someone else already owns a write lock
bail!("Cannot unload chunk because it is locked as writable!")
}
Ok(())
}
/// Sets the chunk as loaded and returns the previous state.
pub fn set_loaded(&self) -> bool {
self.loaded.swap(true, Ordering::SeqCst)
}
/// Locks this chunk with read acccess. Doesn't block.
/// Returns None if the chunk is unloaded or locked for writing, Some otherwise.
pub fn try_read(&self) -> Option<RwLockReadGuard<Chunk>> {
self.lock.try_read()
}
/// Locks this chunk with read acccess, blocking the current thread until it can be acquired.
/// Returns None if the chunk is unloaded, Some otherwise.
pub fn read(&self) -> RwLockReadGuard<Chunk> {
self.lock.read()
}
/// Locks this chunk with exclusive write acccess. Doesn't block.
/// Returns None if the chunk is unloaded or locked already, Some otherwise.
pub fn try_write(&self) -> Option<RwLockWriteGuard<Chunk>> {
if self.is_loaded() {
self.lock.try_write()
} else {
None
}
}
/// Locks this chunk with exclusive write acccess, blocking the current thread until it can be acquired.
/// Returns None if the chunk is unloaded, Some otherwise.
pub fn write(&self) -> Option<RwLockWriteGuard<Chunk>> {
if self.is_loaded() {
Some(self.lock.write())
} else {
None
}
}
pub fn is_locked(&self) -> bool {
self.lock.is_locked()
}
}
#[cfg(test)]
mod tests {
use std::{
thread::{sleep, spawn, JoinHandle},
time::Duration,
};
use libcraft_core::ChunkPosition;
use super::*;
fn empty_lock(x: i32, z: i32, loaded: bool) -> ChunkLock {
ChunkLock::new(Chunk::new(ChunkPosition::new(x, z)), loaded)
}
#[test]
fn normal_function() {
let lock = empty_lock(0, 0, true);
for _ in 0..100 {
// It should be possible to lock in any way
if rand::random::<bool>() {
let _guard = lock.try_read().unwrap();
} else {
let _guard = lock.try_write().unwrap();
}
}
}
#[test]
fn cannot_write_unloaded() {
let lock = empty_lock(0, 0, false);
assert!(lock.try_write().is_none())
}
#[test]
fn can_read_unloaded() {
let lock = empty_lock(0, 0, false);
assert!(lock.try_read().is_some())
}
#[test]
fn multithreaded() {
let lock = Arc::new(empty_lock(0, 0, true));
let mut handles: Vec<JoinHandle<()>> = vec![];
for _ in 0..20 {
let l = lock.clone();
handles.push(spawn(move || {
while let Some(guard) = l.write() {
sleep(Duration::from_millis(10));
drop(guard)
}
}))
}
sleep(Duration::from_millis(1000));
lock.set_unloaded().unwrap_or(()); // Discard error
for h in handles {
h.join().unwrap() // Wait for all threads to stop
}
}
}