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:
Filipe Rodrigues 2021-05-31 06:05:21 +01:00
parent ac0813edc4
commit 4f7a8a149e
8 changed files with 313 additions and 133 deletions

View File

@ -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() {

View File

@ -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

View File

@ -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)),
}

View File

@ -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);

View 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"));

View File

@ -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
View 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!()
}
}

View File

@ -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;