Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VFS support #199

Merged
merged 7 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub enum Error {
/// The path to the file that was unable to be opened.
path: PathBuf,
/// The error that occured when trying to open the file.
err: std::io::Error,
err: Box<dyn std::error::Error>,
},
/// There was an invalid tile in the map parsed.
InvalidTileFound,
Expand Down Expand Up @@ -103,7 +103,7 @@ impl std::error::Error for Error {
Error::DecompressingError(e) => Some(e as &dyn std::error::Error),
Error::Base64DecodingError(e) => Some(e as &dyn std::error::Error),
Error::XmlDecodingError(e) => Some(e as &dyn std::error::Error),
Error::CouldNotOpenFile { err, .. } => Some(err as &dyn std::error::Error),
Error::CouldNotOpenFile { err, .. } => Some(err.as_ref()),
aleokdev marked this conversation as resolved.
Show resolved Hide resolved
_ => None,
}
}
Expand Down
129 changes: 56 additions & 73 deletions src/loader.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
use std::{fs::File, io::Read, path::Path};

use crate::{Error, FilesystemResourceCache, Map, ResourceCache, Result, Tileset};
use crate::{FilesystemResourceCache, Map, ResourceCache, Result, Tileset};

/// A trait defining types that can load data from a [`ResourcePath`](crate::ResourcePath).
///
/// This trait should be implemented if you wish to load data from a virtual filesystem.
///
/// ## Example
/// TODO
pub trait ResourceReader {
/// The type of the resource that the reader provides. For example, for
/// [`FilesystemResourceReader`], this is defined as [`File`].
type Resource: Read;
/// The type that is returned if [`read_from()`](Self::read_from()) fails. For example, for
/// [`FilesystemResourceReader`], this is defined as [`std::io::Error`].
type Error: std::error::Error + 'static;

fn read_from(&mut self, path: &Path) -> std::result::Result<Self::Resource, Self::Error>;
}

/// A [`ResourceReader`] that reads from [`File`] handles.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FilesystemResourceReader;

impl FilesystemResourceReader {
fn new() -> Self {
Self
}
}

impl ResourceReader for FilesystemResourceReader {
type Resource = File;
type Error = std::io::Error;

fn read_from(&mut self, path: &Path) -> std::result::Result<Self::Resource, Self::Error> {
std::fs::File::open(path)
}
}

/// A type used for loading [`Map`]s and [`Tileset`]s.
///
Expand All @@ -12,20 +48,25 @@ use crate::{Error, FilesystemResourceCache, Map, ResourceCache, Result, Tileset}
/// intermediate artifacts, so using a type for creation can ensure that the cache is reused if
/// loading more than one object is required.
#[derive(Debug, Clone)]
pub struct Loader<Cache: ResourceCache = FilesystemResourceCache> {
pub struct Loader<
Cache: ResourceCache = FilesystemResourceCache,
Reader: ResourceReader = FilesystemResourceReader,
> {
cache: Cache,
reader: Reader,
}

impl Loader<FilesystemResourceCache> {
aleokdev marked this conversation as resolved.
Show resolved Hide resolved
/// Creates a new loader, creating a default ([`FilesystemResourceCache`]) resource cache in the process.
pub fn new() -> Self {
Self {
cache: FilesystemResourceCache::new(),
aleokdev marked this conversation as resolved.
Show resolved Hide resolved
reader: FilesystemResourceReader::new(),
}
}
}

impl<Cache: ResourceCache> Loader<Cache> {
impl<Cache: ResourceCache, Reader: ResourceReader> Loader<Cache, Reader> {
/// Creates a new loader using a specific resource cache.
///
/// ## Example
Expand Down Expand Up @@ -70,45 +111,18 @@ impl<Cache: ResourceCache> Loader<Cache> {
/// # Ok(())
/// # }
/// ```
pub fn with_cache(cache: Cache) -> Self {
Self { cache }
pub fn with_cache_and_reader(cache: Cache, reader: Reader) -> Self {
Self { cache, reader }
}

/// Parses a file hopefully containing a Tiled map and tries to parse it. All external files
/// will be loaded relative to the path given.
///
/// All intermediate objects such as map tilesets will be stored in the [internal loader cache].
///
/// If you need to parse a reader object instead, use [Loader::load_tmx_map_from()].
///
/// [internal loader cache]: Loader::cache()
pub fn load_tmx_map(&mut self, path: impl AsRef<Path>) -> Result<Map> {
let reader = File::open(path.as_ref()).map_err(|err| Error::CouldNotOpenFile {
path: path.as_ref().to_owned(),
err,
})?;
crate::parse::xml::parse_map(reader, path.as_ref(), &mut self.cache)
}

/// Parses a map out of a reader hopefully containing the contents of a Tiled file.
///
/// This augments [`load_tmx_map`] with a custom reader: some engines (e.g. Amethyst) simply
/// hand over a byte stream and file location for parsing, in which case this function may be
/// required.
///
/// If you need to parse a file in the filesystem instead, [`load_tmx_map`] might be
/// more convenient.
///
/// The path is used for external dependencies such as tilesets or images. It is required.
/// If the map if fully embedded and doesn't refer to external files, you may input an arbitrary
/// path; the library won't read from the filesystem if it is not required to do so.
///
/// All intermediate objects such as map tilesets will be stored in the [internal loader cache].
///
/// [internal loader cache]: Loader::cache()
/// [`load_tmx_map`]: Loader::load_tmx_map()
pub fn load_tmx_map_from(&mut self, reader: impl Read, path: impl AsRef<Path>) -> Result<Map> {
crate::parse::xml::parse_map(reader, path.as_ref(), &mut self.cache)
crate::parse::xml::parse_map(path.as_ref(), &mut self.reader, &mut self.cache)
}

/// Parses a file hopefully containing a Tiled tileset and tries to parse it. All external files
Expand All @@ -120,52 +134,21 @@ impl<Cache: ResourceCache> Loader<Cache> {
///
/// If you need to parse a reader object instead, use [Loader::load_tsx_tileset_from()].
pub fn load_tsx_tileset(&mut self, path: impl AsRef<Path>) -> Result<Tileset> {
let reader = File::open(path.as_ref()).map_err(|err| Error::CouldNotOpenFile {
path: path.as_ref().to_owned(),
err,
})?;
crate::parse::xml::parse_tileset(reader, path.as_ref())
}

/// Parses a tileset out of a reader hopefully containing the contents of a Tiled tileset.
/// Uses the `path` parameter as the root for any relative paths found in the tileset.
///
/// Unless you specifically want to load a tileset, you won't need to call this function. If
/// you are trying to load a map, simply use [`Loader::load_tmx_map`] or
/// [`Loader::load_tmx_map_from`].
///
/// ## Example
/// ```
/// use std::fs::File;
/// use std::path::PathBuf;
/// use std::io::BufReader;
/// use tiled::Loader;
///
/// let path = "assets/tilesheet.tsx";
/// // Note: This is just an example, if you actually need to load a file use `load_tsx_tileset`
/// // instead.
/// let reader = BufReader::new(File::open(path).unwrap());
/// let mut loader = Loader::new();
/// let tileset = loader.load_tsx_tileset_from(reader, path).unwrap();
///
/// assert_eq!(tileset.image.unwrap().source, PathBuf::from("assets/tilesheet.png"));
/// ```
pub fn load_tsx_tileset_from(
&self,
reader: impl Read,
path: impl AsRef<Path>,
) -> Result<Tileset> {
// This function doesn't need the cache right now, but will do once template support is in
crate::parse::xml::parse_tileset(reader, path.as_ref())
crate::parse::xml::parse_tileset(path.as_ref(), &mut self.reader)
}

/// Returns a reference to the loader's internal [`ResourceCache`].
pub fn cache(&self) -> &Cache {
&self.cache
}

/// Consumes the loader and returns its internal [`ResourceCache`].
pub fn into_cache(self) -> Cache {
self.cache
/// Returns a reference to the loader's internal [`ResourceReader`].
pub fn reader(&self) -> &Reader {
&self.reader
}

/// Consumes the loader and returns its internal [`ResourceCache`] and [`ResourceReader`].
pub fn into_inner(self) -> (Cache, Reader) {
(self.cache, self.reader)
}
}
41 changes: 5 additions & 36 deletions src/map.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Structures related to Tiled maps.

use std::{collections::HashMap, fmt, fs::File, io::Read, path::Path, str::FromStr, sync::Arc};
use std::{collections::HashMap, fmt, path::Path, str::FromStr, sync::Arc};

use xml::attribute::OwnedAttribute;

Expand All @@ -10,7 +10,7 @@ use crate::{
properties::{parse_properties, Color, Properties},
tileset::Tileset,
util::{get_attrs, parse_tag, XmlEventResult},
EmbeddedParseResultType, Layer, ResourceCache,
EmbeddedParseResultType, Layer, ResourceCache, ResourceReader,
};

pub(crate) struct MapTilesetGid {
Expand Down Expand Up @@ -60,38 +60,6 @@ pub struct Map {
}

impl Map {
/// Parse a buffer hopefully containing the contents of a Tiled file and try to
/// parse it. This augments `parse_file` with a custom reader: some engines
/// (e.g. Amethyst) simply hand over a byte stream (and file location) for parsing,
/// in which case this function may be required.
///
/// The path is used for external dependencies such as tilesets or images. It is required.
/// If the map if fully embedded and doesn't refer to external files, you may input an arbitrary path;
/// the library won't read from the filesystem if it is not required to do so.
///
/// The tileset cache is used to store and refer to any tilesets found along the way.
#[deprecated(since = "0.10.1", note = "Use `Loader::load_tmx_map_from` instead")]
pub fn parse_reader<R: Read>(
reader: R,
path: impl AsRef<Path>,
cache: &mut impl ResourceCache,
) -> Result<Self> {
crate::parse::xml::parse_map(reader, path.as_ref(), cache)
}

/// Parse a file hopefully containing a Tiled map and try to parse it. All external
/// files will be loaded relative to the path given.
///
/// The tileset cache is used to store and refer to any tilesets found along the way.
#[deprecated(since = "0.10.1", note = "Use `Loader::load_tmx_map` instead")]
pub fn parse_file(path: impl AsRef<Path>, cache: &mut impl ResourceCache) -> Result<Self> {
let reader = File::open(path.as_ref()).map_err(|err| Error::CouldNotOpenFile {
path: path.as_ref().to_owned(),
err,
})?;
crate::parse::xml::parse_map(reader, path.as_ref(), cache)
}

/// The TMX format version this map was saved to. Equivalent to the map file's `version`
/// attribute.
pub fn version(&self) -> &str {
Expand Down Expand Up @@ -131,6 +99,7 @@ impl Map {
attrs: Vec<OwnedAttribute>,
map_path: &Path,
cache: &mut impl ResourceCache,
reader: &mut impl ResourceReader,
) -> Result<Map> {
let ((c, infinite), (v, o, w, h, tw, th)) = get_attrs!(
attrs,
Expand Down Expand Up @@ -163,8 +132,8 @@ impl Map {
let res = Tileset::parse_xml_in_map(parser, attrs, map_path)?;
match res.result_type {
EmbeddedParseResultType::ExternalReference { tileset_path } => {
let file = File::open(&tileset_path).map_err(|err| Error::CouldNotOpenFile{path: tileset_path.clone(), err })?;
let tileset = cache.get_or_try_insert_tileset_with(tileset_path.clone(), || crate::parse::xml::parse_tileset(file, &tileset_path))?;
let file = reader.read_from(&tileset_path).map_err(|err| Error::CouldNotOpenFile{path: tileset_path.clone(), err: Box::new(err) })?;
aleokdev marked this conversation as resolved.
Show resolved Hide resolved
let tileset = cache.get_or_try_insert_tileset_with(tileset_path.clone(), || crate::parse::xml::parse_tileset(&tileset_path, reader))?;
tilesets.push(MapTilesetGid{first_gid: res.first_gid, tileset});
}
EmbeddedParseResultType::Embedded { tileset } => {
Expand Down
18 changes: 8 additions & 10 deletions src/parse/xml/map.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
use std::{io::Read, path::Path};
use std::{path::Path};

use xml::{reader::XmlEvent, EventReader};

use crate::{Error, Map, ResourceCache, Result};
use crate::{Error, Map, ResourceCache, Result, ResourceReader};

pub fn parse_map(
reader: impl Read,
path: &Path,
reader: &mut impl ResourceReader,
cache: &mut impl ResourceCache,
) -> Result<Map> {
let mut parser = EventReader::new(reader);
let mut parser = EventReader::new(reader.read_from(path).map_err(|err| Error::CouldNotOpenFile {
path: path.to_owned(),
err: Box::new(err),
})?);
loop {
match parser.next().map_err(Error::XmlDecodingError)? {
XmlEvent::StartElement {
name, attributes, ..
} => {
if name.local_name == "map" {
return Map::parse_xml(
&mut parser.into_iter(),
attributes,
path,
cache,
);
return Map::parse_xml(&mut parser.into_iter(), attributes, path, cache, reader);
}
}
XmlEvent::EndDocument => {
Expand Down
16 changes: 12 additions & 4 deletions src/parse/xml/tileset.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
use std::{io::Read, path::Path};
use std::path::Path;

use xml::{reader::XmlEvent, EventReader};

use crate::{Error, Result, Tileset};
use crate::{Error, ResourceReader, Result, Tileset};

pub fn parse_tileset<R: Read>(reader: R, path: &Path) -> Result<Tileset> {
let mut tileset_parser = EventReader::new(reader);
pub fn parse_tileset(path: &Path, reader: &mut impl ResourceReader) -> Result<Tileset> {
let mut tileset_parser =
EventReader::new(
reader
.read_from(path)
.map_err(|err| Error::CouldNotOpenFile {
path: path.to_owned(),
err: Box::new(err),
})?,
);
loop {
match tileset_parser.next().map_err(Error::XmlDecodingError)? {
XmlEvent::StartElement {
Expand Down
22 changes: 0 additions & 22 deletions src/tileset.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::collections::HashMap;
use std::io::Read;
use std::path::{Path, PathBuf};

use xml::attribute::OwnedAttribute;
Expand Down Expand Up @@ -81,27 +80,6 @@ struct TilesetProperties {
}

impl Tileset {
/// Parses a tileset out of a reader hopefully containing the contents of a Tiled tileset.
/// Uses the `path` parameter as the root for any relative paths found in the tileset.
///
/// ## Example
/// ```
/// use std::fs::File;
/// use std::path::PathBuf;
/// use std::io::BufReader;
/// use tiled::Tileset;
///
/// let path = "assets/tilesheet.tsx";
/// let reader = BufReader::new(File::open(path).unwrap());
/// let tileset = Tileset::parse_reader(reader, path).unwrap();
///
/// assert_eq!(tileset.image.unwrap().source, PathBuf::from("assets/tilesheet.png"));
/// ```
#[deprecated(since = "0.10.1", note = "Use `Loader::load_tsx_tileset_from` instead")]
pub fn parse_reader<R: Read>(reader: R, path: impl AsRef<Path>) -> Result<Self> {
crate::parse::xml::parse_tileset(reader, path.as_ref())
}

/// Gets the tile with the specified ID from the tileset.
#[inline]
pub fn get_tile(&self, id: TileId) -> Option<Tile> {
Expand Down