rs-tiled icon indicating copy to clipboard operation
rs-tiled copied to clipboard

Options for more easily opening a map with external files from a special source

Open LaylBongers opened this issue 7 years ago • 5 comments

The problem I'm currently running into is that in ggez, opening files from resources is done through a special Filesystem type, which doesn't give the full path of the map file. I want to provide a way for rs-tiled to find the tilesets using this filesystem type, rather than manually finding the full path of the map file and passing that.

LaylBongers avatar Nov 17 '17 15:11 LaylBongers

Hello, I'm giving a try at making a game in Rust and I also faced this kind of issue. I'm think about using a version of parse that'd also require a function (or any kind of structure) that'd load itself the file for the library to work. Making the glue code would require a bit of elbow ooil but it'd greatly improve this library's compatibility with other asset management.

Have a nice day.

Eiyeron avatar Oct 14 '18 09:10 Eiyeron

Being able to customize this would also enable you to protect against malicious .tmx files opening arbitrary files on disk, which could be a problem if you want to allow any User Generated Content. I'd even argue for some default sanity checks / sandboxing...

This is probably as straightforward as replacing the map_path: Option<&Path> with map_path: Option<&dyn BufReadFactory> (where BufReadFactory is some new trait) in various calls that eventually calls down to:

https://github.com/mattyhall/rs-tiled/blob/06e4ecf4257e4dd0813087e1fcd642d7f0ce7713/src/lib.rs#L432

MaulingMonkey avatar Sep 03 '19 02:09 MaulingMonkey

For bonus points, making this async/future based would make it easier to integrate into the browser I believe.

MaulingMonkey avatar Sep 03 '19 23:09 MaulingMonkey

Trying to address this by supplying a closure for loading external files: https://github.com/mattyhall/rs-tiled/pull/100

koalefant avatar May 07 '21 21:05 koalefant

To deploy on the web I put all the files in the binary and then I implement the loader like this:

pub struct MyTiledResourceCache(HashMap<tiled::ResourcePathBuf, Arc<tiled::Tileset>>);
impl tiled::ResourceCache for MyTiledResourceCache {
	fn get_tileset(&self, path: impl AsRef<tiled::ResourcePath>) -> Option<Arc<tiled::Tileset>> {
		self.0.get(&path.as_ref().to_path_buf()).cloned()
	}
	fn get_or_try_insert_tileset_with<F, E>(
		&mut self,
		path: tiled::ResourcePathBuf,
		f: F
	) -> Result<Arc<tiled::Tileset>, E>
	where
		F: FnOnce() -> Result<tiled::Tileset, E>
	{
		match self.0.entry(path.clone()) {
			Entry::Occupied(o) => Ok(o.get().clone()),
			Entry::Vacant(v) => {
				#[cfg(not(target_arch = "wasm32"))]
				{
					let tileset = f()?;
					Ok(v.insert(Arc::new(tileset)).clone())
				}

				#[cfg(target_arch = "wasm32")]
				{
					let _ = f;
					let loader = tiled::Loader::new();
					// in_memory_tiled_files contains all the map and tileset inside the memory
					let tileset = loader.load_tsx_tileset_from(in_memory_tiled_files::InMemoryFetcher::fetch(path).unwrap(), &"assets/").unwrap();
					Ok(v.insert(Arc::new(tileset)).clone())
				}
			},
		}
	}
}

But I have an issue that even when using this cache rs-tiled tries to open the file before calling get_or_try_insert_tileset. Thus I modified rs-tiled like this:

[thiolliere@fedora rs-tiled]$ git diff
diff --git a/src/map.rs b/src/map.rs
index 8871985..807c9e7 100644
--- a/src/map.rs
+++ b/src/map.rs
@@ -188,8 +188,15 @@ 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 callback = || {
+                            let file = File::open(&tileset_path)
+                                .map_err(|err| Error::CouldNotOpenFile{path: tileset_path.clone(), err })?;
+                            crate::parse::xml::parse_tileset(file, &tileset_path)
+                        };
+                        let tileset = cache.get_or_try_insert_tileset_with(
+                            tileset_path.clone(),
+                            callback,
+                        )?;
                         tilesets.push(MapTilesetGid{first_gid: res.first_gid, tileset});
                     }
                     EmbeddedParseResultType::Embedded { tileset } => {

This way my cache always contains all the external tileset.

I am working on a PR to have this File::open inside the callback on master

EDIT: I see that the branch next already contains a ResourceReader to implement what I have above. Thanks for the good work ResourceReader looks good

gui1117 avatar Sep 15 '22 05:09 gui1117

Pretty much solved in 0.11.0. Closing.

aleokdev avatar May 15 '23 15:05 aleokdev