-
-
Notifications
You must be signed in to change notification settings - Fork 85
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
Static Resources API #321
base: 1.20.2
Are you sure you want to change the base?
Static Resources API #321
Conversation
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 would have designed this a lot differently (see: StaticData mod). There are some core issues I'd like to address here before I'm happy with the API.
Dividing things into Static Resources versus Data is an interesting idea that I'm going to need to put some thought into, I'll definitely have more to say later on that.
Resource getResourceOrThrow(Identifier id) throws FileNotFoundException; | ||
InputStream open(Identifier id) throws IOException; | ||
BufferedReader openAsReader(Identifier id) throws IOException; | ||
Map<Identifier, JsonElement> findJsonObjects(String namespace, String startingPath); |
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.
We've tightly coupled search and decoding here. Even as a helper method, this is awkward. Consider something like
findObjects(Identifier idWithStartingPath).getAsJson();
and while we're at it, consider using the quilt json5 parser instead of the Gson one.
static StaticResourceLoader get(ResourceType type){ | ||
return StaticResourceLoaderImpl.get(type); | ||
} | ||
List<Resource> getAllResources(Identifier id); |
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.
Seems like copying vanilla respacks and datapacks for no benefit. I'd much rather have a good search system and let people search recursively/non-recursively at root than have this method exclusively for a case basically no one needs.
import java.util.stream.Stream; | ||
|
||
/** | ||
* Basically a glorified re-implementation of {@link MultiPackResourceManager} to avoid mixins to said class effecting static data loading. |
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.
affecting*
/** | ||
* Based heavily on {@link MultiPackResourceManagerMixin#quilt$recomputeNamespaces()} | ||
*/ | ||
private void computeNamespaces(){ |
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.
again, seems like a lot of moving parts for what's essentially a copy of a little-used feature from vanilla.
List<Resource> getAllResources(Identifier id); | ||
Set<String> getNamespaces(); | ||
List<ResourcePack> getPacks(); | ||
Map<Identifier, List<Resource>> findAllResources(String startingPath, Predicate<Identifier> pathFilter); |
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.
Is there a reason the static resource doesn't contain provenance information? Is it just to reuse the Resource class? This could bake down into a List or a Stream if we wanted it to, and users could mapreduce it if they actually cared about the properties that make it a map here.
Stream<ResourcePack> streamPacks(); | ||
Optional<Resource> getResource(Identifier id); | ||
Resource getResourceOrThrow(Identifier id) throws FileNotFoundException; | ||
InputStream open(Identifier id) throws IOException; |
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.
Probably tight coupling. Consider
findFirst(id).open();
...y/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/ResourceLoader.java
Outdated
Show resolved
Hide resolved
After quite a bit of refactoring work, I'd say this is finally ready for review. Let's go over what's changed since the first post:
Thank you to everyone who gave feedback and advice in the discord and on here during the process of developing this PR, it wouldn't have been possible to write it otherwise. |
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.
If you are using Intellij, please import the checkstyle file as the code style for this project as several part of the PR are not compliant with the code style of QSL.
* @param type the given resource type | ||
* @return the static resource manager instance | ||
*/ | ||
static @NotNull MultiPackResourceManager getStaticResourceManager(@NotNull ResourceType type){ |
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.
Any reason to return MultiPackResourceManager
and not just ResourceManager
?
So far ResourceManager
is usually used for APIs that provide a resource manager.
Also, given the API structure, this should not be static
but be something to implement directly on the ResourceLoaderImpl
class like every other method present in this interface.
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.
The former I agree with and will be changed accordingly in the next commit, but the latter I have to disagree with—the creation and storage of the StaticResourceManager
s that back the Static Resources API are both entirely static-scoped, so it's both less verbose and more aligned with the data structure of the API itself to get the static resource managers in a static method.
If this is a sticking point I can change it, but I feel like it makes more sense this way.
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'm inclined to agree that this should be static, given that a static resource manager is not tied to a ResourceLoader
in any way
@@ -98,13 +105,11 @@ public final class ResourceLoaderImpl implements ResourceLoader { | |||
.toBooleanOrElse(QuiltLoader.isDevelopmentEnvironment()); | |||
private static final boolean DEBUG_RELOADERS_ORDER = TriState.fromProperty("quilt.resource_loader.debug.reloaders_order") | |||
.toBooleanOrElse(false); | |||
|
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 would highly appreciate to not remove blank lines that are present for readability.
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.
Fixed in next commit, hopefully?
private static List<ResourcePack> findUserStaticPacks() { | ||
List<ResourcePack> returnList = new ArrayList<>(); | ||
File directoryFile = QuiltLoader.getGameDir().resolve(STATIC_PACK_ROOT).toFile(); | ||
File[] potentialPackFiles = directoryFile.listFiles(filterFile -> { |
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.
Given that most things use the NIO Path API, it might make more sense to use the Files
NIO API to search packs.
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.
Tested implementing it that way, and it both works and is slightly cleaner overall so it'll be swapped to using Files.walk
in the next commit where listFiles
is currently.
* @see StaticResourceManager | ||
*/ | ||
@ApiStatus.Internal | ||
public interface StaticResourceManagerWrapper { |
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.
This is useless, a simple instanceof StaticResourceManager
is already sufficient for checking.
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.
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.
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.
You need to cast to object before you instanceof. Java is smart and finds impossible conditions
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.
Yup, turns out it was an 'IDEA and the compiler don't understand mixins' moment and not an actual error—this has been fixed and thus said wrapper interface will be gone in next commit.
MultiPackResourceManager clientManager = ResourceLoader.getStaticResourceManager(ResourceType.CLIENT_RESOURCES); | ||
Resource cronch = clientManager.getResource(new Identifier("cronch", "test_client")).get(); | ||
BufferedReader readerCronch = cronch.openBufferedReader(); | ||
LOGGER.error(readerCronch.readLine() + "(Reading this line should be impossible!)"); |
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.
Please use the var-args system of the logger to avoid passing unsafe strings.
Given this is a test mod it's not as bad, but they're also references on how to use an API, so they should not accidentally give bad practices.
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.
Fixed in next commit.
* @param type the given resource type | ||
* @return the static resource manager instance | ||
*/ | ||
static @NotNull ResourceManager getStaticResourceManager(@NotNull ResourceType type) { |
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.
With the most recent commit, everything should be in order—bar the de-static
ing of this method, which I'm opposed to, though if it's a sticking point for @LambdAurora I can change it. See #321 (comment) for the reasoning on why that hasn't been done.
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.
Given that static resource managers are unrelated to the normal resource loaders, really - which handle non static data - I am also opposed to de-static
ing this
if (pathAsFile.isFile()) { | ||
if (pathAsFile.toPath().toString().endsWith(".zip")) { | ||
returnList.add(new ZipResourcePack(n, pathAsFile, false)); | ||
} else if (!pathAsFile.getName().equals(".DS_Store")) { |
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.
if we're going to silently ignore filesystem helper files "like .DS_Store" instead of ignoring specifically .DS_Store, can we do something like
private static final Set<String> IGNORED_FILES = Set.of(".DS_Store");
...
} else if (!IGNORED_FILES.contains(pathAsFile.getName())) {
...
}
Otherwise, if there will definitely never be any other fs helper files, we could just delete the comment
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.
are thumbs.db files still around?
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.
What does vanilla do for detecting packs in the resource packs folder? I feel like we should probably copy that
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.
Vanilla doesn't actually ignore .DS_Store files and similar (it throws an error if they're inside packs!), but it silently doesn't load loose files as a pack, same as is done here. Telling users that loose files aren't supported is a thing since other static data implementations do allow for them, so it felt pertinent to say 'hey this API doesn't do that'.
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 worth noting that both the macOS and Windows filesystems are case-insensitive by default, so a plain equals won't suffice, either. And... there are many more files like this, including but not limited to ._*
on macos, .directory
on some Linux DEs, desktop.ini
on windows, etc etc
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.
Anyways, the fs helper file thing has been reimplemented with thumbs.db
and .DS_Store
in the set, and has a message to make an issue on the main github if an OS filesystem helper is warned of as a loose file
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 worth noting that both the macOS and Windows filesystems are case-insensitive by default, so a plain equals won't suffice, either. And... there are many more files like this, including but not limited to
._*
on macos,.directory
on some Linux DEs,desktop.ini
on windows, etc etc
Yaaay... eating some food rn but will go back and do another pass on this afterwards—thankfully Set
has a case-insensitive contains method built-in, so that part won't be too big of an issue...
Unless anyone else has suggestions for additions again, this should be ready for re-review by @LambdAurora |
...e/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java
Outdated
Show resolved
Hide resolved
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.
Please space out your code for readability.
...e/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java
Show resolved
Hide resolved
...e/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java
Show resolved
Hide resolved
...e/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java
Outdated
Show resolved
Hide resolved
...e/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java
Show resolved
Hide resolved
...e/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java
Show resolved
Hide resolved
...esource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/StaticResourceManager.java
Outdated
Show resolved
Hide resolved
...oader/src/main/java/org/quiltmc/qsl/resource/loader/mixin/MultiPackResourceManagerMixin.java
Outdated
Show resolved
Hide resolved
...y/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/ResourceLoader.java
Outdated
Show resolved
Hide resolved
...e/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/impl/ResourceLoaderImpl.java
Outdated
Show resolved
Hide resolved
Could we potentially get some more movement on this soonish, now that QFAPI sync is back in order? |
...y/core/resource_loader/src/main/java/org/quiltmc/qsl/resource/loader/api/ResourceLoader.java
Outdated
Show resolved
Hide resolved
Should be ready for 1.20.2 now—I have absolutely no clue why there's conflicts in |
Ready for 1.20.2 again if all checks pass—ResourceLoaderImpl's conflicts are git being weird just like last time |
this might be hard but i would try rebasing your PR off of 1.20.2. That should hopefully fix the conflict |
Have tried doing that and it gets stuck in the infinite loop usually. Might try doing it again later |
efb892f
to
5da1018
Compare
Basically a manual rebase/merge onto upstream 1.20.2—actually doing either resulted in things breaking horribly because git moment Also fixed the quilt.mod.json for the resource_loader package—it still had the old name for the ClientLoaderEventsTestMod test (formerly ClientResourceLoaderEventsTestMod)
This post is inaccurate to what now exists in the API, please see the comment made before marking this PR as ready for review!
This is an API for Static Resources: user-controlled data that is accessible early, scope-independent and does not change after it is loaded.
Motivations
Due to the properties of Static Resources, they have uses in creating data-driven APIs that affect the game's state in a way that does not change, and/or in a way that must be done far before vanilla's Resource/Data Packs become available. This makes a Static Resources API essential for a future Content Pack API, as the registries said API would need to modify are static in of themselves and are frozen early on in the game's loading process, making regular resources (in the form of Data Packs) highly unsuited for such an API.
The
Resource
class and mutability of on-disk filesWhile the first two properties of Static Resources (early access and scope-independence) are already implemented in the current draft, the third property (immutability) is a far more complicated affair, as the needs of each consumer of Static Resources will likely vary heavily in terms of how many times said consumer accesses said resources. Since
Resource
objects are just pointers to a file and this API primarily returnsResource
objects, it is possible for the contents of the file to be modified at runtime, which may cause unexpected behavior for the consumer. To be honest, I (rin) am not sure whether or not ensuring strict immutability of the data provided byResource
instances is something this API should even be concerned with doing—this will need discussion in the Quilt discord and/or in this PR's comments.The API itself: modder & QSL-side
The API is found in the
core/resource_loader
package, and is primarily accessed through twoStaticResourceLoaderImpl
objects. Said objects are accessible outside of the API via the static interface methodStaticResourceLoader#get
, to which a vanillaResourceType
object is provided—while this currently (subject to change if requested—this could easily be enforced) only dictates where the API gets Resources from, it should be treated the same as in vanilla, whereResourceType.CLIENT_RESOURCES
are only available on the client, andResourceType.SERVER_DATA
in all scopes.The implementation of
StaticResourceLoader
(and thus the two objects provided, depending on whether server data or client resources are requested) serves as a fairly thin wrapper around another API-internal class,StaticResourceManager
, which is currently little more than a re-implementation of the vanillaMultiPackResourceManager
class. This was done asMultiPackResourceManager
is an essential part of the very much not static vanilla resource loading process, and is targeted by mixins changing things about said process—using an object of that class (or of a child of that class) for this API would lead to it being affected by those mixins.Currently, consumers of Static Resources are responsible for loading the files provided
Resource
objects point to themselves.The API itself: the stuff exposed to end-users
Static Resources are stored within the
static/resources
andstatic/data
folders: the former being for client-side resources/data and the latter for server-side resources/data. Basically, these folders have a same purpose to vanilla'sresources
folder and thedatapacks
folder in each world save, respectively. Like vanilla resource packs and data packs, the files placed in these folders must be contained in packs as well—though the distinction between resource packs and data packs is less concrete with static packs than it is with vanilla's packs.TODO Checklist before this API is ready for review and merger
StaticResourceLoader
/StaticResourceManagerImpl
) and the manager backing it (StaticResourceManager
)If data immutability should be ensured by the API:
CachedResource
class or something along those lines, some form of hashing of the loaded data, etc