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

Add template support #170

Merged
merged 21 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.11.0]
### Added
- VFS support (#199)
- `cache_mut` loader property (#207)
- Template support!
Templates are loaded automatically when they are encountered, and are treated as intermediate
objects. As such, `ResourceCache` has now methods for both getting and inserting them (#170).
- VFS support (#199).
- `cache_mut` loader property (#207).

### Changed
- `LayerType` variants have been stripped from the `Layer` suffix. (#203)
- `LayerType` variants have been stripped from the `Layer` suffix (#203).
- `ResourceCache::get_or_try_insert_tileset_with` has been replaced by `ResourceCache::insert_tileset`.

## [0.10.2]
### Added
Expand Down
18 changes: 18 additions & 0 deletions assets/tiled_object_template.tmx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.4" tiledversion="1.4.2" orientation="orthogonal" renderorder="right-down" width="3" height="3" tilewidth="32" tileheight="32" infinite="0" nextlayerid="3" nextobjectid="3">
<tileset firstgid="1" source="tilesheet.tsx"/>
<layer id="1" name="Tile Layer 1" width="3" height="3">
<data encoding="csv">
6,7,8,
20,21,22,
34,35,36
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="1" template="tiled_object_template.tx" x="32" y="32">
<properties>
</properties>
</object>
<object id="2" gid="45" x="0" y="32" width="32" height="32"/>
</objectgroup>
</map>
9 changes: 9 additions & 0 deletions assets/tiled_object_template.tx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<tileset firstgid="1" source="tilesheet_template.tsx"/>
<object gid="45" width="32" height="32">
<properties>
<property name="property" type="int" value="1"/>
</properties>
</object>
</template>
12 changes: 12 additions & 0 deletions assets/tilesheet_template.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.4" tiledversion="1.4.0" name="tilesheet_template" tilewidth="32" tileheight="32" tilecount="84" columns="14">
<properties>
<property name="tileset property" value="tsp"/>
</properties>
<image source="tilesheet.png" width="448" height="192"/>
<tile id="1">
<properties>
<property name="a tile property" value="123"/>
</properties>
</tile>
</tileset>
57 changes: 25 additions & 32 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
sync::Arc,
};

use crate::Tileset;
use crate::{Template, Tileset};

/// A reference type that is used to refer to a resource. For the owned variant, see [`ResourcePathBuf`].
pub type ResourcePath = Path;
Expand All @@ -23,49 +23,45 @@ pub trait ResourceCache {
/// # Example
/// ```
/// use std::fs::File;
/// use tiled::{FilesystemResourceReader, Tileset, Loader, ResourceCache};
/// use tiled::{Tileset, Loader, ResourceCache};
/// # use tiled::Result;
/// # use std::sync::Arc;
///
/// # fn main() -> Result<()> {
/// let mut loader = Loader::new();
/// let path = "assets/tilesheet.tsx";
///
/// assert!(loader.cache().get_tileset(path).is_none());
/// loader.load_tmx_map("assets/tiled_base64_external.tmx");
/// let tileset = Arc::new(loader.load_tsx_tileset(path)?);
/// loader.cache_mut().insert_tileset(path, tileset);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe these changes should be done in a different PR.

/// assert!(loader.cache().get_tileset(path).is_some());
/// # Ok(())
/// # }
/// ```
fn get_tileset(&self, path: impl AsRef<ResourcePath>) -> Option<Arc<Tileset>>;

/// Returns the tileset mapped to `path` if it exists, otherwise calls `f` and, depending on its
/// result, it will:
/// - Insert the object into the cache, if the result was [`Ok`].
/// - Return the error and leave the cache intact, if the result was [`Err`].
/// Insert a new tileset into the cache.
///
/// ## Note
/// This function is normally only used internally; there are not many instances where it is
/// callable outside of the library implementation, since the cache is normally owned by the
/// loader anyways.
fn get_or_try_insert_tileset_with<F, E>(
&mut self,
path: ResourcePathBuf,
f: F,
) -> Result<Arc<Tileset>, E>
where
F: FnOnce() -> Result<Tileset, E>;
/// See [`Self::get_tileset()`] for an example.
fn insert_tileset(&mut self, path: impl AsRef<ResourcePath>, tileset: Arc<Tileset>);
/// Obtains a template from the cache, if it exists.
fn get_template(&self, path: impl AsRef<ResourcePath>) -> Option<Arc<Template>>;
/// Insert a new template into the cache.
fn insert_template(&mut self, path: impl AsRef<ResourcePath>, tileset: Arc<Template>);
}

/// A cache that identifies resources by their path, storing a map of them.
#[derive(Debug)]
pub struct DefaultResourceCache {
tilesets: HashMap<ResourcePathBuf, Arc<Tileset>>,
templates: HashMap<ResourcePathBuf, Arc<Template>>,
}

impl DefaultResourceCache {
/// Creates an empty [`DefaultResourceCache`].
pub fn new() -> Self {
Self {
tilesets: HashMap::new(),
templates: HashMap::new(),
}
}
}
Expand All @@ -75,18 +71,15 @@ impl ResourceCache for DefaultResourceCache {
self.tilesets.get(path.as_ref()).map(Clone::clone)
}

fn get_or_try_insert_tileset_with<F, E>(
&mut self,
path: ResourcePathBuf,
f: F,
) -> Result<Arc<Tileset>, E>
where
F: FnOnce() -> Result<Tileset, E>,
{
Ok(match self.tilesets.entry(path) {
std::collections::hash_map::Entry::Occupied(o) => o.into_mut(),
std::collections::hash_map::Entry::Vacant(v) => v.insert(Arc::new(f()?)),
}
.clone())
fn insert_tileset(&mut self, path: impl AsRef<ResourcePath>, tileset: Arc<Tileset>) {
self.tilesets.insert(path.as_ref().to_path_buf(), tileset);
}

fn get_template(&self, path: impl AsRef<ResourcePath>) -> Option<Arc<Template>> {
self.templates.get(path.as_ref()).map(Clone::clone)
}

fn insert_template(&mut self, path: impl AsRef<ResourcePath>, tileset: Arc<Template>) {
self.templates.insert(path.as_ref().to_path_buf(), tileset);
}
}
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub enum Error {
/// Supported types are `string`, `int`, `float`, `bool`, `color`, `file` and `object`.
type_name: String,
},
/// A template was found that does not have an object element in it.
TemplateHasNoObject,
}

/// A result with an error variant of [`crate::Error`].
Expand Down Expand Up @@ -94,6 +96,7 @@ impl fmt::Display for Error {
write!(fmt, "Invalid property value: {}", description),
Error::UnknownPropertyType { type_name } =>
write!(fmt, "Unknown property value type '{}'", type_name),
Error::TemplateHasNoObject => write!(fmt, "A template was found with no object element"),
}
}
}
Expand Down
17 changes: 13 additions & 4 deletions src/layers/group.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use std::collections::HashMap;
use std::path::Path;
use std::{collections::HashMap, path::Path, sync::Arc};

use crate::{
error::Result,
layers::{LayerData, LayerTag},
map::MapTilesetGid,
properties::{parse_properties, Properties},
util::*,
Error, Layer,
Error, Layer, MapTilesetGid, ResourceCache, ResourceReader, Tileset,
};

/// The raw data of a [`GroupLayer`]. Does not include a reference to its parent [`Map`](crate::Map).
Expand All @@ -22,6 +20,9 @@ impl GroupLayerData {
infinite: bool,
map_path: &Path,
tilesets: &[MapTilesetGid],
for_tileset: Option<Arc<Tileset>>,
reader: &mut impl ResourceReader,
cache: &mut impl ResourceCache,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that in many places we're passing around both a reader and a cache. Would it make sense to pass around a loader instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, Loader owns the reader/cache

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does that pose a problem for passing loader: &mut Loader instead of the above two parameters?

) -> Result<(Self, Properties)> {
let mut properties = HashMap::new();
let mut layers = Vec::new();
Expand All @@ -34,6 +35,8 @@ impl GroupLayerData {
infinite,
map_path,
tilesets,
for_tileset.as_ref().cloned(),reader,
cache
)?);
Ok(())
},
Expand All @@ -45,6 +48,8 @@ impl GroupLayerData {
infinite,
map_path,
tilesets,
for_tileset.as_ref().cloned(),reader,
cache
)?);
Ok(())
},
Expand All @@ -56,6 +61,8 @@ impl GroupLayerData {
infinite,
map_path,
tilesets,
for_tileset.as_ref().cloned(),reader,
cache
)?);
Ok(())
},
Expand All @@ -67,6 +74,8 @@ impl GroupLayerData {
infinite,
map_path,
tilesets,
for_tileset.as_ref().cloned(),reader,
cache
)?);
Ok(())
},
Expand Down
30 changes: 26 additions & 4 deletions src/layers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::path::Path;
use std::{path::Path, sync::Arc};

use xml::attribute::OwnedAttribute;

use crate::{error::Result, properties::Properties, util::*, Color, Map, MapTilesetGid};
use crate::{
error::Result, properties::Properties, util::*, Color, Map, MapTilesetGid, ResourceCache,
ResourceReader, Tileset,
};

mod image;
pub use image::*;
Expand Down Expand Up @@ -69,6 +72,9 @@ impl LayerData {
infinite: bool,
map_path: &Path,
tilesets: &[MapTilesetGid],
for_tileset: Option<Arc<Tileset>>,
reader: &mut impl ResourceReader,
cache: &mut impl ResourceCache,
) -> Result<Self> {
let (opacity, tint_color, visible, offset_x, offset_y, parallax_x, parallax_y, name, id) = get_attrs!(
attrs,
Expand All @@ -91,15 +97,31 @@ impl LayerData {
(LayerDataType::Tiles(ty), properties)
}
LayerTag::Objects => {
let (ty, properties) = ObjectLayerData::new(parser, attrs, Some(tilesets))?;
let (ty, properties) = ObjectLayerData::new(
parser,
attrs,
Some(tilesets),
for_tileset,
map_path,
reader,
cache,
)?;
(LayerDataType::Objects(ty), properties)
}
LayerTag::Image => {
let (ty, properties) = ImageLayerData::new(parser, map_path)?;
(LayerDataType::Image(ty), properties)
}
LayerTag::Group => {
let (ty, properties) = GroupLayerData::new(parser, infinite, map_path, tilesets)?;
let (ty, properties) = GroupLayerData::new(
parser,
infinite,
map_path,
tilesets,
for_tileset,
reader,
cache,
)?;
(LayerDataType::Group(ty), properties)
}
};
Expand Down
11 changes: 8 additions & 3 deletions src/layers/object.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::collections::HashMap;
use std::{collections::HashMap, path::Path, sync::Arc};

use xml::attribute::OwnedAttribute;

use crate::{
parse_properties,
util::{get_attrs, map_wrapper, parse_tag, XmlEventResult},
Color, Error, MapTilesetGid, Object, ObjectData, Properties, Result,
Color, Error, MapTilesetGid, Object, ObjectData, Properties, ResourceCache, ResourceReader,
Result, Tileset,
};

/// Raw data referring to a map object layer or tile collision data.
Expand All @@ -23,6 +24,10 @@ impl ObjectLayerData {
parser: &mut impl Iterator<Item = XmlEventResult>,
attrs: Vec<OwnedAttribute>,
tilesets: Option<&[MapTilesetGid]>,
for_tileset: Option<Arc<Tileset>>,
path_relative_to: &Path,
reader: &mut impl ResourceReader,
cache: &mut impl ResourceCache,
) -> Result<(ObjectLayerData, Properties)> {
let c = get_attrs!(
attrs,
Expand All @@ -34,7 +39,7 @@ impl ObjectLayerData {
let mut properties = HashMap::new();
parse_tag!(parser, "objectgroup", {
"object" => |attrs| {
objects.push(ObjectData::new(parser, attrs, tilesets)?);
objects.push(ObjectData::new(parser, attrs, tilesets, for_tileset.as_ref().cloned(), path_relative_to, reader, cache)?);
Ok(())
},
"properties" => |_| {
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod map;
mod objects;
mod parse;
mod properties;
mod template;
mod tile;
mod tileset;
mod util;
Expand All @@ -28,5 +29,6 @@ pub use loader::*;
pub use map::*;
pub use objects::*;
pub use properties::*;
pub use template::*;
pub use tile::*;
pub use tileset::*;
28 changes: 18 additions & 10 deletions src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,24 @@ impl<Cache: ResourceCache, Reader: ResourceReader> Loader<Cache, Reader> {
/// None
/// }
///
/// fn get_or_try_insert_tileset_with<F, E>(
/// &mut self,
/// _path: tiled::ResourcePathBuf,
/// f: F,
/// ) -> Result<std::sync::Arc<tiled::Tileset>, E>
/// where
/// F: FnOnce() -> Result<tiled::Tileset, E>,
/// {
/// f().map(Arc::new)
/// fn get_template(
/// &self,
/// _path: impl AsRef<tiled::ResourcePath>,
/// ) -> Option<std::sync::Arc<tiled::Template>> {
/// None
/// }
///
/// fn insert_tileset(
/// &mut self,
/// _path: impl AsRef<tiled::ResourcePath>,
/// _tileset: Arc<tiled::Tileset>
/// ) {}
///
/// fn insert_template(
/// &mut self,
/// _path: impl AsRef<tiled::ResourcePath>,
/// _template: Arc<tiled::Template>
/// ) {}
/// }
///
/// let mut loader = Loader::with_cache_and_reader(NoopResourceCache, FilesystemResourceReader);
Expand Down Expand Up @@ -140,7 +148,7 @@ impl<Cache: ResourceCache, Reader: ResourceReader> Loader<Cache, Reader> {
/// This function will **not** cache the tileset inside the internal [`ResourceCache`], since
/// in this context it is not an intermediate object.
pub fn load_tsx_tileset(&mut self, path: impl AsRef<Path>) -> Result<Tileset> {
crate::parse::xml::parse_tileset(path.as_ref(), &mut self.reader)
crate::parse::xml::parse_tileset(path.as_ref(), &mut self.reader, &mut self.cache)
}

/// Returns a reference to the loader's internal [`ResourceCache`].
Expand Down
Loading