mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-03 16:16:33 +00:00
Added dcb_io::IoThread.
`dcb_io::GameFile` now just stores a `T` instead of `CdRomCursor<T>`. `GameFile` now has interior threaded mutability.
This commit is contained in:
parent
ac0813edc4
commit
4f7a8a149e
@ -11,7 +11,6 @@ pub use error::{OpenFileError, SwapFilesError};
|
||||
pub use path::Path;
|
||||
|
||||
// Imports
|
||||
use dcb_cdrom_xa::CdRomCursor;
|
||||
use dcb_drv::DirEntryKind;
|
||||
use dcb_util::IoCursor;
|
||||
use std::io;
|
||||
@ -20,7 +19,7 @@ use std::io;
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct GameFile<T> {
|
||||
/// CD-Rom
|
||||
cdrom: CdRomCursor<T>,
|
||||
cdrom: T,
|
||||
}
|
||||
|
||||
// Constants
|
||||
@ -58,7 +57,7 @@ impl<T> GameFile<T> {
|
||||
// Constructors
|
||||
impl<T: io::Read + io::Seek> GameFile<T> {
|
||||
/// Creates a new game file
|
||||
pub fn new(cdrom: CdRomCursor<T>) -> Self {
|
||||
pub fn new(cdrom: T) -> Self {
|
||||
Self { cdrom }
|
||||
}
|
||||
}
|
||||
@ -66,7 +65,7 @@ impl<T: io::Read + io::Seek> GameFile<T> {
|
||||
// Getters
|
||||
impl<T> GameFile<T> {
|
||||
/// Returns the cdrom associated with this game file
|
||||
pub fn cdrom(&mut self) -> &mut CdRomCursor<T> {
|
||||
pub fn cdrom(&mut self) -> &mut T {
|
||||
&mut self.cdrom
|
||||
}
|
||||
}
|
||||
@ -74,7 +73,7 @@ impl<T> GameFile<T> {
|
||||
// Drive getters
|
||||
impl<T: io::Seek> GameFile<T> {
|
||||
/// Returns the `A.DRV` file alongside it's cursor
|
||||
pub fn a_drv(&mut self) -> Result<DriveCursor<&mut CdRomCursor<T>>, io::Error> {
|
||||
pub fn a_drv(&mut self) -> Result<DriveCursor<&mut T>, io::Error> {
|
||||
match DriveCursor::new(&mut self.cdrom, Self::A_OFFSET, Self::A_SIZE) {
|
||||
Ok(cursor) => Ok(cursor),
|
||||
Err(err) => Err(err),
|
||||
@ -82,7 +81,7 @@ impl<T: io::Seek> GameFile<T> {
|
||||
}
|
||||
|
||||
/// Returns the `B.DRV` file alongside it's cursor
|
||||
pub fn b_drv(&mut self) -> Result<DriveCursor<&mut CdRomCursor<T>>, io::Error> {
|
||||
pub fn b_drv(&mut self) -> Result<DriveCursor<&mut T>, io::Error> {
|
||||
match DriveCursor::new(&mut self.cdrom, Self::B_OFFSET, Self::B_SIZE) {
|
||||
Ok(cursor) => Ok(cursor),
|
||||
Err(err) => Err(err),
|
||||
@ -90,7 +89,7 @@ impl<T: io::Seek> GameFile<T> {
|
||||
}
|
||||
|
||||
/// Returns the `C.DRV` file alongside it's cursor
|
||||
pub fn c_drv(&mut self) -> Result<DriveCursor<&mut CdRomCursor<T>>, io::Error> {
|
||||
pub fn c_drv(&mut self) -> Result<DriveCursor<&mut T>, io::Error> {
|
||||
match DriveCursor::new(&mut self.cdrom, Self::C_OFFSET, Self::C_SIZE) {
|
||||
Ok(cursor) => Ok(cursor),
|
||||
Err(err) => Err(err),
|
||||
@ -98,7 +97,7 @@ impl<T: io::Seek> GameFile<T> {
|
||||
}
|
||||
|
||||
/// Returns the `E.DRV` file alongside it's cursor
|
||||
pub fn e_drv(&mut self) -> Result<DriveCursor<&mut CdRomCursor<T>>, io::Error> {
|
||||
pub fn e_drv(&mut self) -> Result<DriveCursor<&mut T>, io::Error> {
|
||||
match DriveCursor::new(&mut self.cdrom, Self::E_OFFSET, Self::E_SIZE) {
|
||||
Ok(cursor) => Ok(cursor),
|
||||
Err(err) => Err(err),
|
||||
@ -106,7 +105,7 @@ impl<T: io::Seek> GameFile<T> {
|
||||
}
|
||||
|
||||
/// Returns the `F.DRV` file alongside it's cursor
|
||||
pub fn f_drv(&mut self) -> Result<DriveCursor<&mut CdRomCursor<T>>, io::Error> {
|
||||
pub fn f_drv(&mut self) -> Result<DriveCursor<&mut T>, io::Error> {
|
||||
match DriveCursor::new(&mut self.cdrom, Self::F_OFFSET, Self::F_SIZE) {
|
||||
Ok(cursor) => Ok(cursor),
|
||||
Err(err) => Err(err),
|
||||
@ -114,7 +113,7 @@ impl<T: io::Seek> GameFile<T> {
|
||||
}
|
||||
|
||||
/// Returns the `G.DRV` file alongside it's cursor
|
||||
pub fn g_drv(&mut self) -> Result<DriveCursor<&mut CdRomCursor<T>>, io::Error> {
|
||||
pub fn g_drv(&mut self) -> Result<DriveCursor<&mut T>, io::Error> {
|
||||
match DriveCursor::new(&mut self.cdrom, Self::G_OFFSET, Self::G_SIZE) {
|
||||
Ok(cursor) => Ok(cursor),
|
||||
Err(err) => Err(err),
|
||||
@ -122,7 +121,7 @@ impl<T: io::Seek> GameFile<T> {
|
||||
}
|
||||
|
||||
/// Returns the `P.DRV` file alongside it's cursor
|
||||
pub fn p_drv(&mut self) -> Result<DriveCursor<&mut CdRomCursor<T>>, io::Error> {
|
||||
pub fn p_drv(&mut self) -> Result<DriveCursor<&mut T>, io::Error> {
|
||||
match DriveCursor::new(&mut self.cdrom, Self::P_OFFSET, Self::P_SIZE) {
|
||||
Ok(cursor) => Ok(cursor),
|
||||
Err(err) => Err(err),
|
||||
@ -133,7 +132,7 @@ impl<T: io::Seek> GameFile<T> {
|
||||
// Files
|
||||
impl<T: io::Seek + io::Read> GameFile<T> {
|
||||
/// Opens a file
|
||||
pub fn open_file(&mut self, path: &Path) -> Result<FileCursor<DriveCursor<&mut CdRomCursor<T>>>, OpenFileError> {
|
||||
pub fn open_file(&mut self, path: &Path) -> Result<FileCursor<DriveCursor<&mut T>>, OpenFileError> {
|
||||
// Check the drive we're accessing.
|
||||
let (drive, path) = path.drive().ok_or(OpenFileError::NoDrive)?;
|
||||
let mut cursor = match drive.as_char() {
|
||||
|
||||
@ -6,14 +6,131 @@ use crate::{
|
||||
};
|
||||
use anyhow::Context;
|
||||
use dcb_cdrom_xa::CdRomCursor;
|
||||
use dcb_util::{IoThread, MutexPoison};
|
||||
use eframe::egui;
|
||||
use std::fs;
|
||||
use std::{fs, sync::Mutex};
|
||||
|
||||
/// Game file
|
||||
pub struct GameFile {
|
||||
/// Game file
|
||||
file: fs::File,
|
||||
file: IoThread<CdRomCursor<fs::File>>,
|
||||
|
||||
/// Drives
|
||||
drives: Mutex<Drives>,
|
||||
}
|
||||
|
||||
impl GameFile {
|
||||
/// Creates a new game
|
||||
pub fn new(file: fs::File) -> Result<Self, anyhow::Error> {
|
||||
let cdrom = CdRomCursor::new(file);
|
||||
let file = IoThread::new(cdrom);
|
||||
let mut game_file = dcb_io::GameFile::new(&file);
|
||||
|
||||
let mut a_reader = game_file.a_drv().context("Unable to get `a` drive")?;
|
||||
let a_tree = DrvTree::new(&mut a_reader).context("Unable to load `a` drive")?;
|
||||
let mut b_reader = game_file.b_drv().context("Unable to get `b` drive")?;
|
||||
let b_tree = DrvTree::new(&mut b_reader).context("Unable to load `b` drive")?;
|
||||
let mut c_reader = game_file.c_drv().context("Unable to get `c` drive")?;
|
||||
let c_tree = DrvTree::new(&mut c_reader).context("Unable to load `c` drive")?;
|
||||
let mut e_reader = game_file.e_drv().context("Unable to get `e` drive")?;
|
||||
let e_tree = DrvTree::new(&mut e_reader).context("Unable to load `e` drive")?;
|
||||
let mut f_reader = game_file.f_drv().context("Unable to get `f` drive")?;
|
||||
let f_tree = DrvTree::new(&mut f_reader).context("Unable to load `f` drive")?;
|
||||
let mut g_reader = game_file.g_drv().context("Unable to get `g` drive")?;
|
||||
let g_tree = DrvTree::new(&mut g_reader).context("Unable to load `g` drive")?;
|
||||
let mut p_reader = game_file.p_drv().context("Unable to get `p` drive")?;
|
||||
let p_tree = DrvTree::new(&mut p_reader).context("Unable to load `p` drive")?;
|
||||
|
||||
let drives = Drives {
|
||||
a_tree,
|
||||
b_tree,
|
||||
c_tree,
|
||||
e_tree,
|
||||
f_tree,
|
||||
g_tree,
|
||||
p_tree,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
drives: Mutex::new(drives),
|
||||
})
|
||||
}
|
||||
|
||||
/// Reloads the game
|
||||
pub fn reload(&self) -> Result<(), anyhow::Error> {
|
||||
let mut game_file = dcb_io::GameFile::new(&self.file);
|
||||
let mut drives = self.drives.lock_unwrap();
|
||||
drives
|
||||
.a_tree
|
||||
.reload(&mut game_file.a_drv().context("Unable to get `A` drive")?)
|
||||
.context("Unable to reload `A` drive")?;
|
||||
drives
|
||||
.b_tree
|
||||
.reload(&mut game_file.b_drv().context("Unable to get `B` drive")?)
|
||||
.context("Unable to reload `B` drive")?;
|
||||
drives
|
||||
.c_tree
|
||||
.reload(&mut game_file.c_drv().context("Unable to get `C` drive")?)
|
||||
.context("Unable to reload `C` drive")?;
|
||||
drives
|
||||
.e_tree
|
||||
.reload(&mut game_file.e_drv().context("Unable to get `E` drive")?)
|
||||
.context("Unable to reload `E` drive")?;
|
||||
drives
|
||||
.f_tree
|
||||
.reload(&mut game_file.f_drv().context("Unable to get `F` drive")?)
|
||||
.context("Unable to reload `F` drive")?;
|
||||
drives
|
||||
.g_tree
|
||||
.reload(&mut game_file.g_drv().context("Unable to get `G` drive")?)
|
||||
.context("Unable to reload `G` drive")?;
|
||||
drives
|
||||
.p_tree
|
||||
.reload(&mut game_file.p_drv().context("Unable to get `P` drive")?)
|
||||
.context("Unable to reload `P` drive")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Displays the game file tree
|
||||
pub fn display(
|
||||
&self, ui: &mut egui::Ui, file_search: &mut String, swap_window: &mut Option<SwapWindow>,
|
||||
) -> DisplayResults {
|
||||
let mut preview_path = None;
|
||||
let mut display_ctx = drv_tree::DisplayCtx {
|
||||
search_str: &file_search,
|
||||
on_file_click: |path: &str| {
|
||||
// If we have a swap window, call it's on file click
|
||||
if let Some(swap_window) = swap_window {
|
||||
swap_window.on_file_click(path);
|
||||
}
|
||||
|
||||
// Then set the path to preview
|
||||
preview_path = Some(path.to_owned());
|
||||
},
|
||||
};
|
||||
|
||||
let drives = self.drives.lock_unwrap();
|
||||
egui::CollapsingHeader::new("A:\\").show(ui, |ui| drives.a_tree.display(ui, "A:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("B:\\").show(ui, |ui| drives.b_tree.display(ui, "B:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("C:\\").show(ui, |ui| drives.c_tree.display(ui, "C:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("E:\\").show(ui, |ui| drives.e_tree.display(ui, "E:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("F:\\").show(ui, |ui| drives.f_tree.display(ui, "F:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("G:\\").show(ui, |ui| drives.g_tree.display(ui, "G:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("P:\\").show(ui, |ui| drives.p_tree.display(ui, "P:\\", &mut display_ctx));
|
||||
|
||||
DisplayResults { preview_path }
|
||||
}
|
||||
|
||||
/// Returns a game file
|
||||
pub fn game_file(&self) -> dcb_io::GameFile<&IoThread<CdRomCursor<fs::File>>> {
|
||||
dcb_io::GameFile::new(&self.file)
|
||||
}
|
||||
}
|
||||
|
||||
/// Drives
|
||||
pub struct Drives {
|
||||
/// `A` drive tree
|
||||
a_tree: DrvTree,
|
||||
|
||||
@ -36,100 +153,6 @@ pub struct GameFile {
|
||||
p_tree: DrvTree,
|
||||
}
|
||||
|
||||
impl GameFile {
|
||||
/// Creates a new game
|
||||
pub fn new(mut file: fs::File) -> Result<Self, anyhow::Error> {
|
||||
let mut game_file = dcb_io::GameFile::new(CdRomCursor::new(&mut file));
|
||||
let mut a_reader = game_file.a_drv().context("Unable to get `a` drive")?;
|
||||
let a_tree = DrvTree::new(&mut a_reader).context("Unable to load `a` drive")?;
|
||||
let mut b_reader = game_file.b_drv().context("Unable to get `b` drive")?;
|
||||
let b_tree = DrvTree::new(&mut b_reader).context("Unable to load `b` drive")?;
|
||||
let mut c_reader = game_file.c_drv().context("Unable to get `c` drive")?;
|
||||
let c_tree = DrvTree::new(&mut c_reader).context("Unable to load `c` drive")?;
|
||||
let mut e_reader = game_file.e_drv().context("Unable to get `e` drive")?;
|
||||
let e_tree = DrvTree::new(&mut e_reader).context("Unable to load `e` drive")?;
|
||||
let mut f_reader = game_file.f_drv().context("Unable to get `f` drive")?;
|
||||
let f_tree = DrvTree::new(&mut f_reader).context("Unable to load `f` drive")?;
|
||||
let mut g_reader = game_file.g_drv().context("Unable to get `g` drive")?;
|
||||
let g_tree = DrvTree::new(&mut g_reader).context("Unable to load `g` drive")?;
|
||||
let mut p_reader = game_file.p_drv().context("Unable to get `p` drive")?;
|
||||
let p_tree = DrvTree::new(&mut p_reader).context("Unable to load `p` drive")?;
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
a_tree,
|
||||
b_tree,
|
||||
c_tree,
|
||||
e_tree,
|
||||
f_tree,
|
||||
g_tree,
|
||||
p_tree,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reloads the game
|
||||
pub fn reload(&mut self) -> Result<(), anyhow::Error> {
|
||||
let mut game_file = dcb_io::GameFile::new(CdRomCursor::new(&mut self.file));
|
||||
self.a_tree
|
||||
.reload(&mut game_file.a_drv().context("Unable to get `A` drive")?)
|
||||
.context("Unable to reload `A` drive")?;
|
||||
self.b_tree
|
||||
.reload(&mut game_file.b_drv().context("Unable to get `B` drive")?)
|
||||
.context("Unable to reload `B` drive")?;
|
||||
self.c_tree
|
||||
.reload(&mut game_file.c_drv().context("Unable to get `C` drive")?)
|
||||
.context("Unable to reload `C` drive")?;
|
||||
self.e_tree
|
||||
.reload(&mut game_file.e_drv().context("Unable to get `E` drive")?)
|
||||
.context("Unable to reload `E` drive")?;
|
||||
self.f_tree
|
||||
.reload(&mut game_file.f_drv().context("Unable to get `F` drive")?)
|
||||
.context("Unable to reload `F` drive")?;
|
||||
self.g_tree
|
||||
.reload(&mut game_file.g_drv().context("Unable to get `G` drive")?)
|
||||
.context("Unable to reload `G` drive")?;
|
||||
self.p_tree
|
||||
.reload(&mut game_file.p_drv().context("Unable to get `P` drive")?)
|
||||
.context("Unable to reload `P` drive")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Displays the game file tree
|
||||
pub fn display(
|
||||
&mut self, ui: &mut egui::Ui, file_search: &mut String, swap_window: &mut Option<SwapWindow>,
|
||||
) -> DisplayResults {
|
||||
let mut preview_path = None;
|
||||
let mut display_ctx = drv_tree::DisplayCtx {
|
||||
search_str: &file_search,
|
||||
on_file_click: |path: &str| {
|
||||
// If we have a swap window, call it's on file click
|
||||
if let Some(swap_window) = swap_window {
|
||||
swap_window.on_file_click(path);
|
||||
}
|
||||
|
||||
// Then set the path to preview
|
||||
preview_path = Some(path.to_owned());
|
||||
},
|
||||
};
|
||||
|
||||
egui::CollapsingHeader::new("A:\\").show(ui, |ui| self.a_tree.display(ui, "A:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("B:\\").show(ui, |ui| self.b_tree.display(ui, "B:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("C:\\").show(ui, |ui| self.c_tree.display(ui, "C:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("E:\\").show(ui, |ui| self.e_tree.display(ui, "E:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("F:\\").show(ui, |ui| self.f_tree.display(ui, "F:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("G:\\").show(ui, |ui| self.g_tree.display(ui, "G:\\", &mut display_ctx));
|
||||
egui::CollapsingHeader::new("P:\\").show(ui, |ui| self.p_tree.display(ui, "P:\\", &mut display_ctx));
|
||||
|
||||
DisplayResults { preview_path }
|
||||
}
|
||||
|
||||
/// Returns a game file
|
||||
pub fn game_file(&mut self) -> dcb_io::GameFile<&mut fs::File> {
|
||||
dcb_io::GameFile::new(CdRomCursor::new(&mut self.file))
|
||||
}
|
||||
}
|
||||
|
||||
/// Display results
|
||||
pub struct DisplayResults {
|
||||
/// Preview path
|
||||
|
||||
@ -19,17 +19,12 @@ pub mod swap_window;
|
||||
|
||||
// Imports
|
||||
use anyhow::Context;
|
||||
use dcb_util::{task, MutexPoison};
|
||||
use dcb_util::task;
|
||||
use eframe::{egui, epi, NativeOptions};
|
||||
use game_file::GameFile;
|
||||
use native_dialog::{FileDialog, MessageDialog, MessageType};
|
||||
use preview_panel::{PreviewPanel, PreviewPanelBuilder};
|
||||
use std::{
|
||||
fs,
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use std::{fs, io::Write, path::PathBuf, sync::Arc};
|
||||
use swap_window::SwapWindow;
|
||||
|
||||
fn main() {
|
||||
@ -52,7 +47,7 @@ pub struct FileEditor {
|
||||
file_path: Option<PathBuf>,
|
||||
|
||||
/// Game file
|
||||
game_file: Option<Arc<Mutex<GameFile>>>,
|
||||
game_file: Option<Arc<GameFile>>,
|
||||
|
||||
/// Game file future
|
||||
game_file_future: Option<task::ValueFuture<Result<GameFile, anyhow::Error>>>,
|
||||
@ -100,7 +95,7 @@ impl epi::App for FileEditor {
|
||||
if let Some(res) = game_file_future.as_mut().and_then(|fut| fut.get()) {
|
||||
*game_file_future = None;
|
||||
match res {
|
||||
Ok(game) => *game_file = Some(Arc::new(Mutex::new(game))),
|
||||
Ok(game) => *game_file = Some(Arc::new(game)),
|
||||
Err(err) => self::alert_error(&format!("Unable to open file: {:?}", err)),
|
||||
};
|
||||
}
|
||||
@ -169,7 +164,7 @@ impl epi::App for FileEditor {
|
||||
// If we have a game file, display it and update the preview
|
||||
if let Some(game_file) = game_file {
|
||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
let results = game_file.lock_unwrap().display(ui, file_search, swap_window);
|
||||
let results = game_file.display(ui, file_search, swap_window);
|
||||
|
||||
// Update the preview if a new file was clicked
|
||||
if let Some(path) = results.preview_path {
|
||||
@ -183,7 +178,7 @@ impl epi::App for FileEditor {
|
||||
preview_panel.display(ctx);
|
||||
|
||||
if let (Some(swap_window), Some(game_file)) = (swap_window, game_file) {
|
||||
swap_window.display(ctx, &mut *game_file.lock_unwrap())
|
||||
swap_window.display(ctx, &*game_file)
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,7 +189,7 @@ impl epi::App for FileEditor {
|
||||
|
||||
// Flush the file if we have it
|
||||
if let Some(game_file) = &mut self.game_file {
|
||||
match game_file.lock_unwrap().game_file().cdrom().flush() {
|
||||
match game_file.game_file().cdrom().flush() {
|
||||
Ok(()) => (),
|
||||
Err(err) => self::alert_error(&format!("Unable to flush file tod isk: {:?}", err)),
|
||||
}
|
||||
|
||||
@ -5,18 +5,12 @@ use crate::GameFile;
|
||||
use anyhow::Context;
|
||||
use dcb_io::game_file::Path;
|
||||
use dcb_tim::{Tim, Tis};
|
||||
use dcb_util::{
|
||||
task::{self, ValueFuture},
|
||||
MutexPoison,
|
||||
};
|
||||
use dcb_util::task::{self, ValueFuture};
|
||||
use eframe::{
|
||||
egui::{self, Color32, TextureId},
|
||||
epi::TextureAllocator,
|
||||
};
|
||||
use std::{
|
||||
io::BufReader,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use std::{io::BufReader, sync::Arc};
|
||||
|
||||
/// Preview panel
|
||||
#[derive(PartialEq, Clone)]
|
||||
@ -146,14 +140,13 @@ pub enum PreviewPanelBuilder {
|
||||
|
||||
impl PreviewPanelBuilder {
|
||||
/// Creates a new builder for a preview Panel
|
||||
pub fn new(game_file: Arc<Mutex<GameFile>>, path: String) -> ValueFuture<Self> {
|
||||
pub fn new(game_file: Arc<GameFile>, path: String) -> ValueFuture<Self> {
|
||||
task::spawn(move || {
|
||||
let res: Result<_, anyhow::Error> = try {
|
||||
match path {
|
||||
path if path.ends_with(".TIM") => {
|
||||
// Deserialize the tim
|
||||
let path = Path::from_ascii(&path).context("Unable to create path")?;
|
||||
let mut game_file = game_file.lock_unwrap();
|
||||
let mut game_file = game_file.game_file();
|
||||
let mut file = game_file.open_file(path).context("Unable to open file")?;
|
||||
let tim = Tim::deserialize(&mut file).context("Unable to parse file")?;
|
||||
@ -177,7 +170,6 @@ impl PreviewPanelBuilder {
|
||||
path if path.ends_with(".TIS") => {
|
||||
// Deserialize the tis
|
||||
let path = Path::from_ascii(&path).context("Unable to create path")?;
|
||||
let mut game_file = game_file.lock_unwrap();
|
||||
let mut game_file = game_file.game_file();
|
||||
let file = game_file.open_file(path).context("Unable to open file")?;
|
||||
let mut file = BufReader::new(file);
|
||||
|
||||
@ -29,7 +29,7 @@ impl SwapWindow {
|
||||
}
|
||||
|
||||
/// Displays this swap window
|
||||
pub fn display(&mut self, ctx: &egui::CtxRef, game_file: &mut GameFile) {
|
||||
pub fn display(&mut self, ctx: &egui::CtxRef, game_file: &GameFile) {
|
||||
egui::Window::new("Swap screen").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(self.first.as_str().unwrap_or("None"));
|
||||
|
||||
@ -26,6 +26,7 @@ ref-cast = "1.0.6"
|
||||
# Thread
|
||||
rayon = "1.5.1"
|
||||
futures = "0.3.15"
|
||||
thread_local = "1.1.3"
|
||||
|
||||
# Logging
|
||||
log = "0.4.14"
|
||||
162
dcb-util/src/io_thread.rs
Normal file
162
dcb-util/src/io_thread.rs
Normal file
@ -0,0 +1,162 @@
|
||||
//! Io thread
|
||||
|
||||
// Imports
|
||||
use crate::MutexPoison;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
convert::{TryFrom, TryInto},
|
||||
io::{self, Cursor, SeekFrom},
|
||||
mem,
|
||||
sync::Mutex,
|
||||
};
|
||||
use thread_local::ThreadLocal;
|
||||
|
||||
/// Io thread
|
||||
///
|
||||
/// This type allows a stream to be read / written to from multiple threads
|
||||
/// each with their own seek pointer.
|
||||
pub struct IoThread<T> {
|
||||
/// Inner
|
||||
inner: Mutex<T>,
|
||||
|
||||
/// Each thread's state
|
||||
threads: ThreadLocal<RefCell<ThreadState>>,
|
||||
}
|
||||
|
||||
impl<T> IoThread<T> {
|
||||
/// Default buffer size
|
||||
const DEFAULT_BUFFER_SIZE: usize = 8192;
|
||||
/// Max buffer size
|
||||
const MAX_BUFFER_SIZE: usize = 0x8000;
|
||||
|
||||
/// Creates a new io thread
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(inner),
|
||||
threads: ThreadLocal::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the thread state associated with this thread
|
||||
fn state(&self) -> &RefCell<ThreadState> {
|
||||
self.threads.get_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread state
|
||||
#[derive(PartialEq, Clone, Default, Debug)]
|
||||
struct ThreadState {
|
||||
/// Buffer base seek
|
||||
base_seek: u64,
|
||||
|
||||
/// Offset from base seek
|
||||
offset: u64,
|
||||
|
||||
/// Current buffer
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ThreadState {
|
||||
/// Returns the base seek as a usize
|
||||
fn base_seek_usize(&self) -> usize {
|
||||
self.base_seek.try_into().expect("`u64` didn't fit into `usize`")
|
||||
}
|
||||
|
||||
/// Returns the offset as a usize
|
||||
fn offset_usize(&self) -> usize {
|
||||
self.offset.try_into().expect("`u64` didn't fit into `usize`")
|
||||
}
|
||||
|
||||
/// Returns the current buffer after the offset
|
||||
fn buffer_offset(&self) -> &[u8] {
|
||||
if self.offset_usize() > self.buffer.len() {
|
||||
return &[];
|
||||
}
|
||||
|
||||
&self.buffer[self.offset_usize()..]
|
||||
}
|
||||
|
||||
/// Returns the current buffer after the offset mutably
|
||||
fn buffer_offset_mut(&mut self) -> &mut [u8] {
|
||||
if self.offset_usize() > self.buffer.len() {
|
||||
return &mut [];
|
||||
}
|
||||
|
||||
let offset = self.offset_usize();
|
||||
&mut self.buffer[offset..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: io::Seek> io::Seek for &'a IoThread<T> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
let mut state = self.state().borrow_mut();
|
||||
|
||||
// TODO: If seek is nearby, don't discard buffer
|
||||
match pos {
|
||||
SeekFrom::Start(pos) => {
|
||||
state.base_seek = pos;
|
||||
state.offset = 0;
|
||||
state.buffer.clear();
|
||||
},
|
||||
SeekFrom::End(pos) => {
|
||||
let len = self.inner.lock_unwrap().stream_len()?;
|
||||
state.base_seek = crate::signed_offset(len, pos);
|
||||
state.offset = 0;
|
||||
state.buffer.clear();
|
||||
},
|
||||
SeekFrom::Current(offset) => {
|
||||
state.offset = crate::signed_offset(state.offset, offset);
|
||||
},
|
||||
};
|
||||
|
||||
Ok(state.base_seek + state.offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: io::Seek + io::Read> io::Read for &'a IoThread<T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let mut state = self.state().borrow_mut();
|
||||
|
||||
// Read all we have in buffer
|
||||
let buffer = state.buffer_offset();
|
||||
let bytes_read = Cursor::new(buffer).read(buf)?;
|
||||
state.offset += u64::try_from(bytes_read).expect("Unable to get `usize` as `u64`");
|
||||
|
||||
// If we read everything, return
|
||||
if bytes_read == buf.len() {
|
||||
return Ok(bytes_read);
|
||||
}
|
||||
|
||||
// If reading the rest onto the buffer would exceed max size, clear the buffer
|
||||
// and set the base offset to the current position.
|
||||
if state.buffer.len() + buf.len() - bytes_read >= IoThread::<T>::MAX_BUFFER_SIZE {
|
||||
state.buffer.clear();
|
||||
state.base_seek += state.offset;
|
||||
state.offset = 0;
|
||||
}
|
||||
|
||||
// Then read all the remaining bytes onto the buffer
|
||||
let remaining_bytes = usize::max(buf.len() - bytes_read, IoThread::<T>::DEFAULT_BUFFER_SIZE);
|
||||
let new_len = usize::min(state.offset_usize() + remaining_bytes, IoThread::<T>::MAX_BUFFER_SIZE);
|
||||
state.buffer.resize(new_len, 0);
|
||||
|
||||
// TODO: Maybe just use `read`?
|
||||
let mut inner = self.inner.lock_unwrap();
|
||||
inner.seek(SeekFrom::Start(state.base_seek + state.offset))?;
|
||||
inner.read_exact(state.buffer_offset_mut())?;
|
||||
|
||||
// Then recurse
|
||||
mem::drop(state);
|
||||
self.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: io::Seek + io::Write> io::Write for &'a IoThread<T> {
|
||||
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,12 @@
|
||||
//! Dcb utilities
|
||||
// Features
|
||||
#![feature(slice_index_methods, format_args_capture)]
|
||||
#![feature(
|
||||
slice_index_methods,
|
||||
format_args_capture,
|
||||
seek_stream_len,
|
||||
unboxed_closures,
|
||||
fn_traits
|
||||
)]
|
||||
// Lints
|
||||
#![warn(clippy::restriction, clippy::pedantic, clippy::nursery)]
|
||||
// We'll disable the ones we don't need
|
||||
@ -68,6 +74,7 @@ pub mod display_wrapper;
|
||||
pub mod family;
|
||||
pub mod impl_bytes;
|
||||
pub mod io_cursor;
|
||||
pub mod io_thread;
|
||||
pub mod lock_poison;
|
||||
pub mod map_box;
|
||||
pub mod next_from_bytes;
|
||||
@ -88,6 +95,7 @@ pub use discarding_sorted_merge_iter::DiscardingSortedMergeIter;
|
||||
pub use display_wrapper::DisplayWrapper;
|
||||
pub use family::ResultFamily;
|
||||
pub use io_cursor::IoCursor;
|
||||
pub use io_thread::IoThread;
|
||||
pub use lock_poison::{MutexPoison, RwLockPoison};
|
||||
pub use map_box::MapBoxResult;
|
||||
pub use next_from_bytes::NextFromBytes;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user