Skip to content

Commit

Permalink
expose filesystem layer
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasf committed Jun 4, 2024
1 parent 56651c6 commit 348cecc
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 27 deletions.
69 changes: 56 additions & 13 deletions src/filesystem.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,68 @@
//! Traits to provide a custom filesystem implementation.
use std::{
fs, io,
path::{Path, PathBuf},
};

use positioned_io::{RandomAccessFile, ReadAt as _};
use positioned_io::ReadAt as _;

/// An abstract filesystem.
pub trait Filesystem: Send + Sync {
/// Determines the size in bytes of the given file.
///
/// Follows symbolic links.
///
/// # Errors
///
/// See [`std::fs::metadata()`]. Additionally errors if `path` does not
/// ultimately point to a regular file.
fn regular_file_size(&self, path: &Path) -> io::Result<u64>;

/// Returns a list of files in the given directory.
///
/// # Errors
///
/// See [`std::fs::read_dir()`].
fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>>;
fn open(&self, path: &Path) -> io::Result<Box<dyn File>>;

/// Opens the given file, returning a handle for random read requests.
///
/// # Errors
///
/// See [`std::fs::File::open()`].
fn open(&self, path: &Path) -> io::Result<Box<dyn RandomAccessFile>>;
}

#[derive(Debug, Copy, Clone)]
/// The purpose of a read. Advisory only.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum ReadHint {
/// Reading metadata from the table file header.
Header,
/// Reading to sparse index to jump close to the correct entry in the block
/// length table.
SparseIndex,
/// Reading the block length table.
BlockLengths,
/// Reading a compressed block.
Data,
/// Reading the DTZ value map.
DtzMap,
}

pub trait File: Send + Sync {
/// An abstract randomly readable file.
pub trait RandomAccessFile: Send + Sync {
/// Reads some bytes starting from a given offset.
///
/// See [`std::os::unix::fs::FileExt::read_at()`] for precise semantics.
fn read_at(&self, hint: ReadHint, pos: u64, buf: &mut [u8]) -> io::Result<usize>;

/// Reads the exact number of bytes required to fill `buf` from the given
/// offset.
///
/// See [`std::os::unix::fs::FileExt::read_exact_at()`] for
/// precise semantics.
fn read_exact_at(&self, hint: ReadHint, mut pos: u64, mut buf: &mut [u8]) -> io::Result<()> {
while !buf.is_empty() {
match self.read_at(hint, pos, buf) {
Expand All @@ -45,22 +85,25 @@ pub trait File: Send + Sync {
Ok(())
}

/// Reads the single byte at a given offset.
fn read_u8_at(&self, hint: ReadHint, pos: u64) -> io::Result<u8> {
let mut buf = [0];
self.read_exact_at(hint, pos, &mut buf[..])?;
Ok(buf[0])
}

/// Reads two bytes at a given offset, returning an integer in little
/// endian.
fn read_u16_le_at(&self, hint: ReadHint, pos: u64) -> io::Result<u16> {
let mut buf = [0; 2];
self.read_exact_at(hint, pos, &mut buf[..])?;
Ok(u16::from_le_bytes(buf))
}
}

pub struct StdFilesystem;
pub(crate) struct DefaultFilesystem;

impl Filesystem for StdFilesystem {
impl Filesystem for DefaultFilesystem {
fn regular_file_size(&self, path: &Path) -> io::Result<u64> {
let meta = path.metadata()?;
if !meta.is_file() {
Expand All @@ -79,19 +122,19 @@ impl Filesystem for StdFilesystem {
.collect()
}

fn open(&self, path: &Path) -> io::Result<Box<dyn File>> {
Ok(Box::new(StdFile {
file: RandomAccessFile::open(path)?,
fn open(&self, path: &Path) -> io::Result<Box<dyn RandomAccessFile>> {
Ok(Box::new(DefaultRandomAccessFile {
inner: positioned_io::RandomAccessFile::open(path)?,
}))
}
}

pub struct StdFile {
file: RandomAccessFile,
pub(crate) struct DefaultRandomAccessFile {
inner: positioned_io::RandomAccessFile,
}

impl File for StdFile {
impl RandomAccessFile for DefaultRandomAccessFile {
fn read_at(&self, _hint: ReadHint, pos: u64, buf: &mut [u8]) -> io::Result<usize> {
self.file.read_at(pos, buf)
self.inner.read_at(pos, buf)
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

#[macro_use]
mod errors;
mod filesystem;
pub mod filesystem;
mod material;
mod table;
mod tablebase;
Expand Down
21 changes: 10 additions & 11 deletions src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ use tracing::{trace, trace_span};

use crate::{
errors::{ProbeError, ProbeResult},
filesystem,
filesystem::ReadHint,
filesystem::{RandomAccessFile, ReadHint},
material::Material,
types::{DecisiveWdl, MaybeRounded, Metric, Pieces, Syzygy, Wdl, MAX_PIECES},
};
Expand Down Expand Up @@ -396,7 +395,7 @@ impl Consts {
}

/// Read the magic header bytes that identify a tablebase file.
fn read_magic_header(raf: &dyn filesystem::File) -> ProbeResult<[u8; 4]> {
fn read_magic_header(raf: &dyn RandomAccessFile) -> ProbeResult<[u8; 4]> {
let mut buf = [0; 4];
if let Err(error) = raf.read_exact_at(ReadHint::Header, 0, &mut buf) {
match error.kind() {
Expand Down Expand Up @@ -429,7 +428,7 @@ fn offdiag(sq: Square) -> bool {

/// Parse a piece list.
fn parse_pieces(
raf: &dyn filesystem::File,
raf: &dyn RandomAccessFile,
ptr: u64,
count: usize,
side: Color,
Expand Down Expand Up @@ -554,7 +553,7 @@ enum DtzMap {
}

impl DtzMap {
fn read(&self, raf: &dyn filesystem::File, wdl: DecisiveWdl, res: u16) -> ProbeResult<u16> {
fn read(&self, raf: &dyn RandomAccessFile, wdl: DecisiveWdl, res: u16) -> ProbeResult<u16> {
let wdl = match wdl {
DecisiveWdl::Win => 0,
DecisiveWdl::Loss => 1,
Expand Down Expand Up @@ -639,7 +638,7 @@ struct PairsData {

impl PairsData {
pub fn parse<S: Syzygy, T: TableTag>(
raf: &dyn filesystem::File,
raf: &dyn RandomAccessFile,
mut ptr: u64,
groups: GroupData,
) -> ProbeResult<(PairsData, u64)> {
Expand Down Expand Up @@ -760,7 +759,7 @@ impl PairsData {

/// Build the symbol table.
fn read_symbols(
raf: &dyn filesystem::File,
raf: &dyn RandomAccessFile,
btree: u64,
symbols: &mut Vec<Symbol>,
visited: &mut [bool],
Expand Down Expand Up @@ -809,7 +808,7 @@ struct Table<T: TableTag, P: Position + Syzygy> {
is_wdl: PhantomData<T>,
syzygy: PhantomData<P>,

raf: Box<dyn filesystem::File>,
raf: Box<dyn RandomAccessFile>,

num_unique_pieces: u8,
min_like_man: u8,
Expand All @@ -835,7 +834,7 @@ impl<T: TableTag, S: Position + Syzygy> Table<T, S> {
/// Panics if the `material` configuration is not supported by Syzygy
/// tablebases (more than 7 pieces or side without pieces).
#[track_caller]
pub fn new(raf: Box<dyn filesystem::File>, material: &Material) -> ProbeResult<Table<T, S>> {
pub fn new(raf: Box<dyn RandomAccessFile>, material: &Material) -> ProbeResult<Table<T, S>> {
let material = material.clone();
assert!(material.count() <= MAX_PIECES);
assert!(material.by_color.white.count() >= 1);
Expand Down Expand Up @@ -1457,7 +1456,7 @@ pub struct WdlTable<S: Position + Syzygy> {
}

impl<S: Position + Syzygy> WdlTable<S> {
pub fn new(raf: Box<dyn filesystem::File>, material: &Material) -> ProbeResult<WdlTable<S>> {
pub fn new(raf: Box<dyn RandomAccessFile>, material: &Material) -> ProbeResult<WdlTable<S>> {
Table::new(raf, material).map(|table| WdlTable { table })
}

Expand All @@ -1473,7 +1472,7 @@ pub struct DtzTable<S: Position + Syzygy> {
}

impl<S: Position + Syzygy> DtzTable<S> {
pub fn new(raf: Box<dyn filesystem::File>, material: &Material) -> ProbeResult<DtzTable<S>> {
pub fn new(raf: Box<dyn RandomAccessFile>, material: &Material) -> ProbeResult<DtzTable<S>> {
Table::new(raf, material).map(|table| DtzTable { table })
}

Expand Down
13 changes: 11 additions & 2 deletions src/tablebase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use tracing::trace_span;

use crate::{
errors::{ProbeResultExt as _, SyzygyError, SyzygyResult},
filesystem::{Filesystem, StdFilesystem},
filesystem::{DefaultFilesystem, Filesystem},
material::Material,
table::{DtzTable, WdlTable},
types::{DecisiveWdl, Dtz, MaybeRounded, Metric, Syzygy, Wdl},
Expand Down Expand Up @@ -60,13 +60,22 @@ impl<S: Position + Clone + Syzygy> Tablebase<S> {
/// Create an empty collection of tables.
pub fn new() -> Tablebase<S> {
Tablebase {
filesystem: Box::new(StdFilesystem),
filesystem: Box::new(DefaultFilesystem),
wdl: FxHashMap::with_capacity_and_hasher(145, Default::default()),
dtz: FxHashMap::with_capacity_and_hasher(145, Default::default()),
max_pieces: 0,
}
}

/// Create an empty collection of tables, providing a custom filesystem
/// implementation.
pub fn with_filesystem(filesystem: Box<dyn Filesystem>) -> Tablebase<S> {
Tablebase {
filesystem,
..Tablebase::new()
}
}

/// Returns the maximum number of pieces over all added tables.
///
/// This number is updated when adding table files and very fast to read.
Expand Down

0 comments on commit 348cecc

Please sign in to comment.