-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Adding AssetPath::resolve() method. #9528
Conversation
Welcome, new contributor! Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨ |
Reviewers: Please pay special attention to the doc comment. I've tried to explain the behavior as clearly as I can. The motivation for this change is largely driven by the need for assets that internally contain references to other assets. Allowing a standardized way of representing relative relationships makes assets more portable, since they can be moved around in the asset directly without having to adjust all the paths. |
crates/bevy_asset/src/path.rs
Outdated
/// If the relative path begins with `#`, then it is considered an asset label, in which case | ||
/// the result is the base path with the label portion replaced. | ||
/// | ||
/// If the relative path starts with "./" or "../", then the result is combined with the base |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another nice feature would be to resolve paths relative to the the same directory of the current AssetPath
. So rather than specifying ./foo.png
or ./foo/bar.txt
, users only need to put foo.png
or foo/bar.txt
(where the ./
is implied).
This would mean that the given relative_path
would pretty much always need to be considered relative. In bevy_proto
, I made it so that a leading /
denoted a path relative to the asset root, but I don't think that has to be the case here since this method is meant for relative paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not meant for relative paths only - an asset pointing to another asset ought to be able to use both relative and "full" (I'm avoiding the word "absolute") paths interchangeably.
As far as using the leading slash, I have two objections: first, I really want the rules to be the same as the rules for combining URIs, which many developers are likely to be familiar with. Second, a leading slash really makes me think of an absolute filesystem path, which is not what this is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, if I were going to do as you suggest, I wouldn't call this method resolve
, I would call it join
or concat
or something along those lines.
The design is inspired by the node.js function path.resolve(), although that function is more complicated and is much more of a swiss army knife than this one is (for one thing, it takes an arbitrary number of arguments).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. In that case, maybe we can split the join functionality you already have here into a dedicated AssetPath::join
method? Or maybe that can happen as a followup PR?
I would have suggested the /
trick that I mentioned earlier, but I don't know how well that fits in the context of Bevy's AssetPath
proper.
Also, I would suggest renaming the relative_path
argument to path
or something similar. The relative
might throw off users who think that the given path will always be relative to the base path, even though that's not the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First let me say - you're not wrong.
Relative URIs show up in a lot of places: clicking on hyperlinks, XML XPointers, JSON-Schema files, and others I could name. (Thus the word "Universal" in the name). There isn't a consistent treatment as to how these are resolved, however it generally is one of two choices, which are the two choices under discussion here. For example, the behavior of clicking on a relative link is different than the behavior of new URL(relative_path, base_path)
in JavaScript. For a link, a relative path that starts with a name, and not a special character, is resolved relative to the document root. For new URL()
, it's resolved relative to the base path.
For Bevy, I think the decision should be whatever is going to be the most useful for the common use cases we anticipate. And also to make sure that we document the behavior clearly.
I'm open to adding a join
method in a subsequent PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have renamed the argument as you have requested. I have also re-written the doc comment a bit more to hopefully clarify some potential ambiguities.
Do we need anyone else to weigh in on this? |
In general, Bevy requires two community approvals for any non-trivial PR. In this particular case, I'm waiting for Cart to weigh in (or at least for his #8624 to merge) to avoid horrible merge conflicts. |
@MrGVSV @alice-i-cecile I think once this gets committed I want to add an additional feature, which is to allow relative labels as well as relative paths. The syntax would be the same as JPointer (which is used by json-schema): "#./abc/def". Here's the use case: Say I have a widget, such as a slider, which accepts a reference to a style asset. Now, the slider has internal parts such as a track, thumb, and so on. These all need styles, so each part needs an asset reference. I'm assuming that there's no CSS-like selector matching, so each part needs it's style specified explicitly. At the same time, however, I want to be able to apply different styles to the slider, so the style asset needs to be a parameter to the slider. In fact, I want to be able to ship a library of "unstyled" widgets that other people can use - I supply the widget behavior, and other people add a new coat of paint for their game. The behavior and the appearance can be authored by different people. However, it's too cumbersome if they have to pass in a separate asset path for each slider part. The answer: relative labels. I design my slider so that all of the parts derive their styles from the base path of the style: "#./slider-thumb", "#./slider-track" and so on. So long as the user who is instantiating the slider has a style asset whose internal labels match the naming scheme, they only have to pass in a single asset path. |
I like that design :) Agreed on leaving it for a follow-up PR though. |
f3b99de
to
bc86105
Compare
I've gone ahead and rebased the PR against the latest HEAD (including the Asset V2 work). Also, I wanted to mention, that it is not intended that relative paths be supported automatically by the asset loading system. Rather, these methods are a convenience for people using assets and writing asset loaders, who can decide whether or not to support relative paths, and more importantly, can decide for themselves what they consider to be the "base path". For example, in the use case I mentioned above, the path resolution happens at the time when a UI template is instantiated, not when it is loaded - because the base path would in that scenario be a parameter that is passed in to the template. |
I've rebased the PR against the most recent changes to main. I'm a bit concerned about the number of calls to |
Given that we are covering "relative asset paths" here, I think its worth discussing the plans I have for Asset Paths in Bevy Asset V2 / Multiple Asset Sources to make sure this aligns. We will need to be able to properly support and track "relative asset paths" to enable moving asset folders without reprocessing them. When possible, assets should load other assets using relative paths so we can encode that information during processing. Relative asset paths are also important for Multiple Asset Sources because a loaded asset should not need to know the name of the asset source it currently exists in. Currently some asset stored in Some basic principles I think we'll want to adopt for paths going forward:
Note that (1) is currently treated like an absolute path in the current implementation. Making this relative would be a breaking change, but in practice, given that it is generally used in the context of From my perspective, "resolve" translates roughly to "for the current AssetPath context / working directory, resolve the final path given a path". Or put another way " With that context out of the way, lets consider the results of the Here are some samples from the tests in this PR:
Another test case that isn't covered: |
@cart So I agree with most of your points. As is typical with these kinds of cases, a contributor may have a certain timidity when it comes to making breaking changes, whereas a maintainer may have no such limitation :) We will need to add a predicate function 'isAbsolute' or 'isFullPath' that returns true if a string begins with '/' or 'scheme:/'. I think the greatest area of contention has to do with the treatment of leaf segments in the base path. Specifically, say I have a base path such as
Note that this second behavior is consistent with IETF RFC 1808, which defines the behavior of relative URL paths: https://datatracker.ietf.org/doc/html/rfc1808 . Specifically, in section 4, step 6 it says:
(It then goes on to describe how the path is normalized by removing './' and '../' segments.) |
Now that I have actually taken the time to read RFC 1808, I think that it should be the canonical reference for this feature, rather than |
7507997
to
5fcb24f
Compare
I have updated the PR to change the behavior to more closely conform to RFC 1808. This addresses some of the issues raised by @cart . However, the PR doesn't yet handle the "asset source" syntax, mainly because I don't have a formal definition of what characters are valid in that case. I suspect that At the very least, I need a function that can extract the "scheme" part of the path, given an Also, the latest changes means that the algorithm can no longer panic. According to RFC 1808, if there are insufficient segments in the base path to match the ".." segments in the relative path, then any leftover ".." elements are left as-is. |
I pretty strongly believe that the swapping behavior is going to confuse people. I've spent a lot of time working with "path code" in a variety of OS-es/languages/frameworks/terminals and this is the first time I've encountered this behavior. I believe others will feel the same way. Additionally, I think people will reach for
I don't dislike this behavior. The alternative (which might be preferable) is to have two variants: |
I know we already discussed this on Discord, but for posterity: on the topic of "asset source": that code hasn't been merged yet. It exists in the Multiple Asset Sources PR. |
@cart Here's a demonstration. Hover over this link and look at the resulting URL. The actual link I just typed is The resulting URL is If I were to implement the behavior that you describe, the resulting URL would be Some other examples: |
Im definitely not disputing that this is how the spec works. Only that it is not how most people are trained to think about paths generally in the context of I think if you were to survey Bevy users (or programmers generally) "how do the following paths resolve", you would get the following answers:
|
I personally believe that catering to user expectations is the correct move here. I think people will reach for (and therefore we should build) a "path joining api that handles |
Maybe we should just call it |
Although |
I think that the use case you are thinking of is different from the use case I am thinking of. The fundamental problem I'm trying to solve is embedded paths within assets. I'm not interested in building a generic asset path resolver, except to the extent that it is necessary to solve the former problem. This means that I'm trying to optimize for user expectations around embedded paths, not user expectations around concatenating paths. These are different things, and, as I will argue, have very little overlap in terms of user experience and expectations. I believe that for most programmers, their experience with embedded paths will derive from working with web technologies. This includes not just HTML hyperlinks, but JavaScript/TypeScript import statements and URLs embedded within CSS. Take for example, the following TypeScript code: import { MyComponent } from "./MyComponent"; The path "./MyComponent" is resolved relative to the base path. But what is the base path? Is it the JavaScript file, or is it the directory containing the JavaScript file? MDN doesn't say, so let's put this question aside for a moment. Similarly, in a CSS file: .button {
background-image: url(./images/button.png)
} While not every web developer will have written a relative hyperlink, you would be hard-pressed not to find a web developer that has not embedded an image in a stylesheet, or imported a JavaScript module. On the flip side, most users who work with filesystems and desktop APIs won't have nearly as much experience dealing with embedded paths, because there are few desktop file formats that contain embedded paths, and those that do are generally not human readable (e.g. Blender files). Contrast with the web, where the vast majority of content formats are both human readable, and can contain links ("hyperlinks") to other content. Users of desktop APIs do have experience with concatenating paths, and I suspect much more so than web developers, because on the web most path concatenation is automatic and implicit. In JS, for example, it's very rare to call the browser API So my first point is that I believe that embedded paths within assets should behave like embedded URIs on the web. I think that the "user expectations" argument favors this. However, that alone does not specify how I'm assuming that the "base path" for resolving embedded paths is the same as the path to the asset itself, because that is what is currently available in OK, I'm going to stop here, because I want to know whether we agree on the behavior of embedded paths before we discuss the question of how resolve should work. |
Hmmm ok I see where your head is at. People have definitely been trained to think about paths/uris this way within the context of embedded paths / web resources. That being said, I think theres a reason the Node.js path API behaves the way it does (without the "swapping" behavior): it is operating within the context of a file system. That is also why it is consistent with "command line apis" and how Rust handles Paths: fn main() {
std::fs::create_dir_all("foo/bar/baz");
std::fs::create_dir_all("foo/baz");
// prints Ok("/playground/foo/bar/baz")
println!("{:?}", std::path::Path::new("foo/bar").join("./baz").canonicalize());
// prints Ok("/playground/foo/baz")
println!("{:?}", std::path::Path::new("foo/bar").join("../baz").canonicalize());
} On the web URIs are not a filesystem. Consider On the other hand, AssetPaths are a virtual filesystem. When you see the AssetPath I think we can reasonably cater to both use cases by making // unwrap is necessary for AssetPath because not all paths will have a parent
load_context.asset_path().parent().unwrap().resolve("./foo");
// we can add an accessor. Internally asset_path.unwrap() is safe because the asset's path cannot be empty
load_context.parent_asset_path().resolve("./foo") Note that Multiple Asset Sources makes it possible to express folder-only AssetPaths and adds support for |
I'm down for |
OK I've updated to the latest head, resolved the conflicts, and coded two versions, I also changed the logic a little bit such as that ".." segments are now treated the same in both the base path and the relative path (this is more consistent, the reason for not doing it before is that I was lazy). I'm a little bit concerned about using The one thing I have not done yet is added support for detecting asset sources as absolute paths. I'm trying to decide if I should leverage |
BTW, |
084679a
to
5439485
Compare
Updated the PR. It now understands asset sources - any path string that begins with a source is considered absolute. This uses
In this latest version, the leading slash Later we can introduce an Reviewers: one thing to note is that I'm using a lot of calls to |
I'm very cool with this / feel similarly. Imo this would probably mean:
|
There are a number of refinements we can make...but "better is the enemy of good enough". I think we have convergence on the main points of the API, there's only nits left to pick. BTW, I did not take your suggestion of adding a "try_resolve" - the reason is that |
I added some doc examples (pictures are worth a thousand words) and resolved my resolve_internal comments. |
@cart Release blurb: given that it's not actually used yet, I'm not sure it needs a release blurb. However, if you want one: "AssetPath now has methods for resolving relative paths. This allow a group of linked assets containing embedded references to be moved around in the filesystem without breaking the links. Asset loaders can voluntarily choose to use these new methods." |
# Objective Fixes bevyengine#9473 ## Solution Added `resolve()` method to AssetPath. This method accepts a relative asset path string and returns a "full" path that has been resolved relative to the current (self) path. --------- Co-authored-by: Carter Anderson <[email protected]>
# Objective Fixes bevyengine#9473 ## Solution Added `resolve()` method to AssetPath. This method accepts a relative asset path string and returns a "full" path that has been resolved relative to the current (self) path. --------- Co-authored-by: Carter Anderson <[email protected]>
Objective
Fixes #9473
Solution
Added
resolve()
method to AssetPath. This method accepts a relative asset path string and returns a "full" path that has been resolved relative to the current (self) path.