alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
root crates client src screenshot mod.rs
use std::fs::{File, OpenOptions};
use std::io::{self, BufWriter};
use std::path::Path;
use std::sync::mpsc::{self, Receiver, Sender, SyncSender};
use std::thread::JoinHandle;
use crate::assets::RgbaImage;
pub struct Screenshots {
sender: Option<SyncSender<RgbaImage>>,
messages: Sender<String>,
results: Receiver<String>,
worker: Option<JoinHandle<()>>,
}
impl Screenshots {
pub fn new(game_dir: &Path) -> io::Result<Self> {
let directory = game_dir.join("screenshots");
let (sender, images) = mpsc::sync_channel::<RgbaImage>(2);
let (messages, results) = mpsc::channel();
let worker = std::thread::Builder::new()
.name("screenshots".into())
.spawn({
let messages = messages.clone();
move || {
for image in images {
let message = match save(&directory, &image) {
Ok(name) => format!("Saved screenshot as {name}"),
Err(error) => format!("Failed to save: {error}"),
};
tracing::info!("{message}");
let _ = messages.send(message);
}
}
})?;
Ok(Self {
sender: Some(sender),
messages,
results,
worker: Some(worker),
})
}
pub fn submit(&self, images: Vec<RgbaImage>) {
for image in images {
if self.sender.as_ref().unwrap().try_send(image).is_err() {
let message = "Failed to save: screenshot queue is full";
tracing::warn!("{message}");
let _ = self.messages.send(message.into());
}
}
}
pub fn submit_on_shutdown(&self, images: Vec<RgbaImage>) {
for image in images {
if let Err(error) = self.sender.as_ref().unwrap().send(image) {
tracing::error!("cannot finish screenshot: {error}");
}
}
}
/// The results of saving, and full queue errors.
pub fn messages(&self) -> impl Iterator<Item = String> + '_ {
self.results.try_iter()
}
}
impl Drop for Screenshots {
fn drop(&mut self) {
self.sender.take();
if let Some(worker) = self.worker.take() {
let _ = worker.join();
}
}
}
/// Writes the image to the first free `<time>.png` or `<time>_<n>.png` and returns its file name.
fn save(directory: &Path, image: &RgbaImage) -> io::Result<String> {
std::fs::create_dir_all(directory)?;
let stamp = chrono::Local::now().format("%Y-%m-%d_%H.%M.%S");
for suffix in 1_u64.. {
let name = match suffix {
1 => format!("{stamp}.png"),
_ => format!("{stamp}_{suffix}.png"),
};
let path = directory.join(&name);
let file = match OpenOptions::new().write(true).create_new(true).open(&path) {
Ok(file) => file,
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => continue,
Err(error) => return Err(error),
};
return write_png(file, image).map(|()| name).inspect_err(|_| {
let _ = std::fs::remove_file(&path);
});
}
unreachable!()
}
fn write_png(file: File, image: &RgbaImage) -> io::Result<()> {
let mut encoder = png::Encoder::new(BufWriter::new(file), image.width, image.height);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header()?;
writer.write_image_data(&image.pixels)?;
Ok(writer.finish()?)
}