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

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

Closed
LaylBongers opened this issue Nov 17, 2017 · 6 comments · Fixed by #199
Closed
Labels
Milestone

Comments

@LaylBongers
Copy link

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.

@Eiyeron
Copy link

Eiyeron commented Oct 14, 2018

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.

@MaulingMonkey
Copy link

MaulingMonkey commented Sep 3, 2019

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
Copy link

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

@koalefant
Copy link

Trying to address this by supplying a closure for loading external files: #100

@gui1117
Copy link

gui1117 commented Sep 15, 2022

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

@aleokdev
Copy link
Contributor

Pretty much solved in 0.11.0. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
7 participants