forked from feather-rs/feather
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchunkworker.rs
More file actions
169 lines (144 loc) · 4.93 KB
/
Copy pathchunkworker.rs
File metadata and controls
169 lines (144 loc) · 4.93 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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! This module handles the asynchronous loading of chunks from
//! region files. It runs on a separate thread and receives
//! load requests over a channel. Upon receiving a request,
//! it attempts to load the chunk from the world directory.
//! If the chunk was loaded successfully, it will send
//! the chunk back over a channel. If the chunk did not exist,
//! it will notify the server thread of the incident.
//! In response, the server thread should generate the chunk.
use crossbeam::channel::{Receiver, Sender};
use feather_core::region;
use feather_core::region::{RegionHandle, RegionPosition};
use feather_core::world::chunk::Chunk;
use feather_core::world::ChunkPosition;
use hashbrown::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
pub type Reply = (ChunkPosition, Result<Chunk, Error>);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Request {
LoadChunk(ChunkPosition),
ShutDown,
}
#[derive(Debug)]
pub enum Error {
ChunkNotExist,
LoadError(region::Error),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Error::ChunkNotExist => {
f.write_str("The specified chunk does not exist in the world save")?
}
Error::LoadError(e) => {
f.write_str("Error loading chunk: ")?;
e.fmt(f)?;
}
}
Ok(())
}
}
/// An open region file
struct RegionFile {
/// The handle for the file
handle: RegionHandle,
/// The timestamp of the last time
/// this region file was used. This
/// value is used to close
/// the file after it isn't used for
/// some period of time.
///
/// TODO actually implement this
_last_used: u64,
}
struct ChunkWorker {
/// The directory in which the world
/// resides
dir: String,
/// Channel used to send chunks and errors
/// back to the server thread
sender: Sender<Reply>,
/// Channel used to receive chunk load requests
/// from the server thread
receiver: Receiver<Request>,
/// A map of currently open region files
open_regions: HashMap<RegionPosition, RegionFile>,
}
/// Starts a chunk worker on a new thread.
/// The returned channels can be used
/// to communicate with the worker.
pub fn start(world_dir: &str) -> (Sender<Request>, Receiver<Reply>) {
let (request_tx, request_rx) = crossbeam::channel::unbounded();
let (reply_tx, reply_rx) = crossbeam::channel::unbounded();
let worker = ChunkWorker {
dir: world_dir.to_string(),
sender: reply_tx,
receiver: request_rx,
open_regions: HashMap::new(),
};
// Without changing the stack size,
// a stack overflow occurs here.
// This seeks to solve that.
std::thread::Builder::new()
.stack_size(1024 * 1024 * 5)
.name("Chunk Worker Thread".to_string())
.spawn(move || run(worker))
.expect("Unable to start chunk worker thread");
(request_tx, reply_rx)
}
/// Runs the chunk worker on the current thread,
/// blocking indefinitely.
fn run(mut worker: ChunkWorker) {
while let Ok(request) = worker.receiver.recv() {
match request {
Request::ShutDown => break,
Request::LoadChunk(pos) => {
let reply = load_chunk(&mut worker, pos);
worker.sender.send(reply).unwrap();
}
}
}
info!("Chunk worker terminating");
}
/// Attempts to load the chunk at the specified position.
fn load_chunk(worker: &mut ChunkWorker, pos: ChunkPosition) -> Reply {
let rpos = RegionPosition::from_chunk(pos);
if !is_region_loaded(worker, pos) {
// Need to load region into memory
let handle = region::load_region(&worker.dir, rpos);
if handle.is_err() {
return (pos, Err(Error::LoadError(handle.err().unwrap())));
}
let handle = handle.unwrap();
let last_used = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let file = RegionFile {
handle,
_last_used: last_used,
};
worker.open_regions.insert(rpos, file);
}
// Load from region file
let file = worker.open_regions.get_mut(&rpos).unwrap();
let handle = &mut file.handle;
load_chunk_from_handle(pos, handle)
}
fn load_chunk_from_handle(pos: ChunkPosition, handle: &mut RegionHandle) -> Reply {
let result = handle.load_chunk(pos);
match result {
Ok(chunk) => (pos, Ok(chunk)),
Err(e) => match e {
region::Error::ChunkNotExist => (pos, Err(Error::ChunkNotExist)),
err => (pos, Err(Error::LoadError(err))),
},
}
}
/// Returns whether the given chunk's region
/// is already loaded.
fn is_region_loaded(worker: &ChunkWorker, chunk_pos: ChunkPosition) -> bool {
worker
.open_regions
.contains_key(&RegionPosition::from_chunk(chunk_pos))
}