-
Notifications
You must be signed in to change notification settings - Fork 755
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move texture existance checks to a new faster util
Instead of using hasResource for existance, take advantage of the fact our existance has many checks per folder instead of each texture being in unique folders. This allows us to lookup all textures in a folder once and cache the results Still want to see if I can further optimize as zip file listResources requires iterating the entire pack, will look into that in the future
- Loading branch information
1 parent
46c8454
commit f41c5df
Showing
4 changed files
with
191 additions
and
39 deletions.
There are no files selected for viewing
132 changes: 132 additions & 0 deletions
132
src/main/java/slimeknights/tconstruct/library/client/model/DynamicTextureLoader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package slimeknights.tconstruct.library.client.model; | ||
|
||
import lombok.extern.log4j.Log4j2; | ||
import net.minecraft.client.Minecraft; | ||
import net.minecraft.client.resources.model.Material; | ||
import net.minecraft.resources.ResourceLocation; | ||
import net.minecraft.server.packs.resources.ResourceManager; | ||
import net.minecraft.world.inventory.InventoryMenu; | ||
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent; | ||
import net.minecraftforge.client.event.TextureStitchEvent; | ||
import net.minecraftforge.common.MinecraftForge; | ||
import net.minecraftforge.eventbus.api.EventPriority; | ||
import slimeknights.mantle.data.IEarlySafeManagerReloadListener; | ||
|
||
import java.util.Collection; | ||
import java.util.HashSet; | ||
import java.util.Set; | ||
import java.util.function.Predicate; | ||
|
||
/** | ||
* Logic to handle dynamic texture scans. Instead of calling {@link ResourceManager#hasResource(ResourceLocation)} per texture, we take advantage of the fact | ||
* our models are always looking for many textures in a single folder and do a per folder call to {@link ResourceManager#listResources(String, Predicate)} | ||
*/ | ||
@Log4j2 | ||
public class DynamicTextureLoader { | ||
/** Start of the path to trim when caching existing textures */ | ||
private static final int TRIM_START = "textures/".length(); | ||
/** End of the path to trim when caching existing textures */ | ||
private static final int TRIM_END = ".png".length(); | ||
/** Set of all folders that have been scanned, so we can avoid scanning them twice */ | ||
private static final Set<String> SCANNED_FOLDERS = new HashSet<>(); | ||
/** Map of discovered textures */ | ||
private static final Set<ResourceLocation> EXISTING_TEXTURES = new HashSet<>(); | ||
/** Set of all textures that are missing from the resource pack, to avoid logging twice */ | ||
private static final Set<ResourceLocation> SKIPPED_TEXTURES = new HashSet<>(); | ||
/** Reload listener to clear caches */ | ||
private static final IEarlySafeManagerReloadListener RELOAD_LISTENER = manager -> { | ||
clearCache(); | ||
}; | ||
|
||
/** Clears all cached texture names */ | ||
private static void clearCache() { | ||
SCANNED_FOLDERS.clear(); | ||
EXISTING_TEXTURES.clear(); | ||
SKIPPED_TEXTURES.clear(); | ||
} | ||
|
||
/** Registers this manager */ | ||
public static void init(RegisterClientReloadListenersEvent event) { | ||
event.registerReloadListener(RELOAD_LISTENER); | ||
// clear cache on texture stitch, no longer need it then as its too late to lookup textures | ||
MinecraftForge.EVENT_BUS.addListener(EventPriority.NORMAL, false, TextureStitchEvent.Pre.class, e -> clearCache()); | ||
} | ||
|
||
/** Checks if the given folder is not yet scanned */ | ||
private static boolean checkFolderNotScanned(String originalFolder) { | ||
// if we already checked the folder, no work to do | ||
if (SCANNED_FOLDERS.contains(originalFolder)) { | ||
return false; | ||
} | ||
// if a folder has not been scanned yet, check if any of its parent's have been scanned | ||
// list resources will fetch all sub folders, so this saves us calling it multiple times per tool | ||
String folder = originalFolder; | ||
int lastPos = folder.lastIndexOf('/'); | ||
while (lastPos != -1) { | ||
folder = folder.substring(0, lastPos); | ||
if (SCANNED_FOLDERS.contains(folder)) { | ||
// if we scanned a parent, no work to do, but mark ourself as scanned, may find more textures here later | ||
SCANNED_FOLDERS.add(originalFolder); | ||
return false; | ||
} | ||
lastPos = folder.lastIndexOf('/'); | ||
} | ||
// mark this folder as searched for next time, return true to fetch texture names | ||
SCANNED_FOLDERS.add(originalFolder); | ||
return true; | ||
} | ||
|
||
/** Checks if a texture exists */ | ||
private static boolean textureExists(ResourceManager manager, String folder, ResourceLocation location) { | ||
if (checkFolderNotScanned(folder)) { | ||
manager.listResources("textures/" + folder, name -> name.endsWith(".png")).stream() | ||
.map(loc -> { | ||
String path = loc.getPath(); | ||
return new ResourceLocation(loc.getNamespace(), path.substring(TRIM_START, path.length() - TRIM_END)); | ||
}) | ||
.forEach(EXISTING_TEXTURES::add); | ||
} | ||
return EXISTING_TEXTURES.contains(location); | ||
} | ||
|
||
/** Logs that a dynamic texture is missing, config option to disable */ | ||
public static void logMissingTexture(ResourceLocation location) { | ||
if (!SKIPPED_TEXTURES.contains(location)) { | ||
SKIPPED_TEXTURES.add(location); | ||
log.debug("Skipping loading texture '{}' as it does not exist in the resource pack", location); | ||
} | ||
} | ||
|
||
/** | ||
* Gets a consumer to add textures to the given collection | ||
* @param allTextures Collection of textures | ||
* @param folder Folder the texture is expected to reside in. Will give inconsistent behavior if the location is not a member of the folder, this is not validated | ||
* @param logMissingTextures If true, log textures that were not found | ||
* @return Texture consumer | ||
*/ | ||
public static Predicate<Material> getTextureAdder(String folder, Collection<Material> allTextures, boolean logMissingTextures) { | ||
ResourceManager manager = Minecraft.getInstance().getResourceManager(); | ||
return mat -> { | ||
// either must be non-blocks, or must exist. We have fallbacks if it does not exist | ||
ResourceLocation loc = mat.texture(); | ||
if (!InventoryMenu.BLOCK_ATLAS.equals(mat.atlasLocation()) || textureExists(manager, folder, loc)) { | ||
allTextures.add(mat); | ||
return true; | ||
} | ||
if (logMissingTextures) { | ||
logMissingTexture(loc); | ||
} | ||
return false; | ||
}; | ||
} | ||
|
||
/** Gets the folder containing the given texture */ | ||
public static String getTextureFolder(ResourceLocation location) { | ||
String path = location.getPath(); | ||
int index = path.lastIndexOf('/'); | ||
if (index == -1) { | ||
return path; | ||
} | ||
return path.substring(0, index); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters