Skip to content

Commit

Permalink
Merge branch 'DM-Earth:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
JieningYu authored Dec 3, 2023
2 parents fd84456 + 457c142 commit 29a9e60
Show file tree
Hide file tree
Showing 13 changed files with 1,000 additions and 194 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Rust

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

env:
CARGO_TERM_COLOR: always

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: cargo build --workspace
- name: Run tests
run: cargo test --verbose --workspace
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ Cargo.lock

**/*.rs.bk
*.pdb

/.vscode
**/.test
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ async-trait = "0.1"
futures-lite = "2.0"
bytes = "1.5"
dashmap = "5.5"

[workspace]
resolver = "2"
members = ["tokio-fs"]
118 changes: 84 additions & 34 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,66 @@
/// Module containing range mappings.
mod range;

/// Module containing in-memory IO handlers for testing.
#[cfg(test)]
pub mod mem_io_handle;

/// Module containing world implementation.
pub mod world;
mod world;

/// Module containing tests.
#[cfg(test)]
mod tests;

use std::ops::Deref;

use async_trait::async_trait;
use futures_lite::{AsyncRead, AsyncWrite};
use futures_lite::AsyncRead;

pub use range::Error as RangeError;
pub use world::{iter::Iter, iter::Lazy, Chunk, Chunks, Dim, Select, World};

/// Represents types stored directly in a dimensional world.
pub trait Data: Sized + Send + Sync + Unpin {
/// Count of dimensions.
const DIMS: usize;

/// Gets the value of given dimension.
/// Gets the value of required dimension.
///
/// Dimension index starts from 0, which should be
/// a unique data such as the only id.
fn value_of(&self, dim: usize) -> u64;
fn dim(&self, dim: usize) -> u64;

/// Decode this type from given `Read` and dimensional values.
fn decode<B: bytes::Buf>(dims: &[u64], buf: B) -> std::io::Result<Self>;

/// Encode this type into bytes buffer.
///
/// Note: You don't need to encode dimensional values.
/// To implementors: You don't need to encode dimensional values.
/// They will be encoded automatically.
fn encode<B: bytes::BufMut>(&self, buf: B) -> std::io::Result<()>;
}

impl<const DIMS: usize> Data for [u64; DIMS] {
const DIMS: usize = DIMS;

#[inline]
fn dim(&self, dim: usize) -> u64 {
self[dim]
}

fn decode<B: bytes::Buf>(dims: &[u64], _buf: B) -> std::io::Result<Self> {
let mut this = [0; DIMS];
for (t, d) in this.iter_mut().zip(dims.iter()) {
*t = *d
}
Ok(this)
}

#[inline]
fn encode<B: bytes::BufMut>(&self, _buf: B) -> std::io::Result<()> {
Ok(())
}
}

/// Trait representing IO handlers for dimensional worlds.
#[async_trait]
pub trait IoHandle: Send + Sync {
Expand All @@ -44,47 +69,72 @@ pub trait IoHandle: Send + Sync {
where
Self: 'a;

/// Type of writer.
type Write<'a>: WriteFinish + Unpin + Send + Sync + 'a
where
Self: 'a;
/// Hints if the chunk with given position is valid.
///
/// If the chunk is hinted by valid, the world will
/// load it from this handler.
#[inline]
fn hint_is_valid(&self, pos: &[usize]) -> bool {
let _ = pos;
true
}

/// Gets reader for given chunk position.
async fn read_chunk<const DIMS: usize>(
&self,
pos: [usize; DIMS],
) -> std::io::Result<Self::Read<'_>>;

/// Gets writer for given chunk position.
async fn write_chunk<const DIMS: usize>(
&self,
pos: [usize; DIMS],
) -> std::io::Result<Self::Write<'_>>;
}

/// Trait representing IO types that perform
/// some async actions after all bytes are wrote.
pub trait WriteFinish: AsyncWrite {
/// Polls the async action.
fn poll_finish(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>>;
impl<P, T> IoHandle for P
where
T: IoHandle + 'static,
P: Deref<Target = T> + Send + Sync,
{
type Read<'a> = T::Read<'a> where Self: 'a;

#[inline]
fn hint_is_valid(&self, pos: &[usize]) -> bool {
self.deref().hint_is_valid(pos)
}

#[doc = " Gets reader for given chunk position."]
#[must_use]
#[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
#[inline]
fn read_chunk<'life0, 'async_trait, const DIMS: usize>(
&'life0 self,
pos: [usize; DIMS],
) -> ::core::pin::Pin<
Box<
dyn ::core::future::Future<Output = std::io::Result<Self::Read<'_>>>
+ ::core::marker::Send
+ 'async_trait,
>,
>
where
'life0: 'async_trait,
Self: 'async_trait,
{
self.deref().read_chunk(pos)
}
}

/// Represents error variants produced by this crate.
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("chunk position out of bound: {0}")]
PosOutOfBound(RangeError),
/// IO Error.
#[error("io err: {0}")]
Io(std::io::Error),
#[error("requesting value has been taken")]
ValueTaken,

/// Requesting value not found.
#[error("requesting value not found")]
ValueNotFound,
#[error("requested stream updated.")]
IterUpdated {
expected: usize,
current: Option<usize>,
},

/// Given value out of range.
#[error("value {value} out of range [{}, {}]", range.0, range.1)]
ValueOutOfRange { range: (u64, u64), value: u64 },
}

/// Type alias for result produced by this crate.
type Result<T> = std::result::Result<T, Error>;
72 changes: 43 additions & 29 deletions src/mem_io_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,82 @@ use std::collections::HashMap;
use async_trait::async_trait;
use futures_lite::{AsyncRead, AsyncWrite};

use crate::{IoHandle, WriteFinish};
use crate::IoHandle;

/// A simple in-memory storage implementing [`IoHandle`].
///
/// This is only for testing.
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct MemStorage {
chunks: async_lock::RwLock<HashMap<String, Vec<u8>>>,
}

#[async_trait]
impl IoHandle for MemStorage {
type Read<'a> = Reader<'a> where Self: 'a;

type Write<'a> = Writer<'a> where Self: 'a;
impl MemStorage {
/// Creates a new [`MemStorage`].
#[inline]
pub fn new() -> Self {
Default::default()
}

async fn read_chunk<const DIMS: usize>(
/// Writes a chunk to this storage.
pub async fn write_chunk<const DIMS: usize>(
&self,
pos: [usize; DIMS],
) -> std::io::Result<Self::Read<'_>> {
) -> std::io::Result<Writer<'_>> {
let mut chunk = String::new();
for dim in pos.iter() {
chunk.push_str(&dim.to_string());
chunk.push('_');
}
chunk.pop();

Ok(Reader {
chunks: self.chunks.read().await,
let mut write = self.chunks.write().await;
write.insert(chunk.to_owned(), vec![]);

Ok(Writer {
chunks: write,
chunk,
})
}
}

async fn write_chunk<const DIMS: usize>(
#[async_trait]
impl IoHandle for MemStorage {
type Read<'a> = Reader<'a> where Self: 'a;

async fn read_chunk<const DIMS: usize>(
&self,
pos: [usize; DIMS],
) -> std::io::Result<Self::Write<'_>> {
) -> std::io::Result<Self::Read<'_>> {
let mut chunk = String::new();
for dim in pos.iter() {
chunk.push_str(&dim.to_string());
chunk.push('_');
}
chunk.pop();

Ok(Writer {
chunks: self.chunks.write().await,
let read = self.chunks.read().await;
if !read.contains_key(&chunk) {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"chunk not found",
));
}

Ok(Reader {
chunks: read,
chunk,
ptr: 0,
})
}
}

/// A reader for [`Mem`].
/// A reader for [`MemStorage`].
#[derive(Debug)]
pub struct Reader<'a> {
chunks: async_lock::RwLockReadGuard<'a, HashMap<String, Vec<u8>>>,
chunk: String,
ptr: usize,
}

impl AsyncRead for Reader<'_> {
Expand All @@ -69,8 +89,12 @@ impl AsyncRead for Reader<'_> {
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
let this = self.get_mut();
let mut chunk = &this.chunks.get(&this.chunk).unwrap()[..];
futures_lite::AsyncRead::poll_read(std::pin::Pin::new(&mut chunk), cx, buf)
let mut chunk = &this.chunks.get(&this.chunk).unwrap()[this.ptr..];
let res = futures_lite::AsyncRead::poll_read(std::pin::Pin::new(&mut chunk), cx, buf);
if let std::task::Poll::Ready(Ok(len)) = res {
this.ptr += len;
}
res
}

#[inline]
Expand All @@ -85,7 +109,7 @@ impl AsyncRead for Reader<'_> {
}
}

/// A writer for [`Mem`].
/// A writer for [`MemStorage`].
#[derive(Debug)]
pub struct Writer<'a> {
chunks: async_lock::RwLockWriteGuard<'a, HashMap<String, Vec<u8>>>,
Expand Down Expand Up @@ -145,13 +169,3 @@ impl AsyncWrite for Writer<'_> {
)
}
}

impl WriteFinish for Writer<'_> {
#[inline]
fn poll_finish(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
15 changes: 4 additions & 11 deletions src/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl DimMapping {
}

/// Gets the chunk position of a value in this mapping.
pub fn chunk_of(&self, mut value: u64) -> Result<usize, Error> {
pub fn chunk_of(&self, mut value: u64) -> crate::Result<usize> {
self.in_range(value)?;
value -= *self.range.start();
let pos = (value / self.spacing) as usize;
Expand All @@ -45,7 +45,7 @@ impl DimMapping {
}

/// Gets range of chunks from given range bounds in this mapping.
pub fn chunks_of(&self, range: impl RangeBounds<u64>) -> Result<RangeInclusive<usize>, Error> {
pub fn chunks_of(&self, range: impl RangeBounds<u64>) -> crate::Result<RangeInclusive<usize>> {
Ok(match range.start_bound() {
std::ops::Bound::Included(value) => self.chunk_of(*value)?,
std::ops::Bound::Excluded(value) => self.chunk_of(*value + 1)?,
Expand All @@ -58,11 +58,11 @@ impl DimMapping {
}

#[inline]
pub fn in_range(&self, value: u64) -> Result<(), Error> {
pub fn in_range(&self, value: u64) -> crate::Result<()> {
if self.range.contains(&value) {
Ok(())
} else {
Err(Error::ValueOutOfRange {
Err(crate::Error::ValueOutOfRange {
range: (*self.range.start(), *self.range.end()),
value,
})
Expand All @@ -75,13 +75,6 @@ impl DimMapping {
}
}

/// Error type for `DimMapping`.
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("value {value} out of range [{}, {}]", range.0, range.1)]
ValueOutOfRange { range: (u64, u64), value: u64 },
}

#[cfg(test)]
mod single_dim_map_tests {
use super::DimMapping;
Expand Down
Loading

0 comments on commit 29a9e60

Please sign in to comment.