From d36c5fcf8e0008dd9433640f90bf6645775c1d03 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Fri, 24 May 2019 10:24:01 +0200 Subject: [PATCH] add sprite atlas path hooks, path-specific events (#195) --- fabric-textures-v0/build.gradle | 2 +- .../client/ClientSpriteRegistryCallback.java | 41 ++++++---- .../texture/SpriteAtlasTextureHooks.java | 23 ++++++ .../texture/SpriteRegistryCallbackHolder.java | 48 ++++++++++++ .../texture/MixinSpriteAtlasTexture.java | 75 ++++++++++++------- .../client/texture/MixinTextureManager.java | 36 +++++++++ .../resources/fabric-textures-v0.mixins.json | 3 +- 7 files changed, 183 insertions(+), 45 deletions(-) create mode 100644 fabric-textures-v0/src/main/java/net/fabricmc/fabric/impl/client/texture/SpriteAtlasTextureHooks.java create mode 100644 fabric-textures-v0/src/main/java/net/fabricmc/fabric/impl/client/texture/SpriteRegistryCallbackHolder.java create mode 100644 fabric-textures-v0/src/main/java/net/fabricmc/fabric/mixin/client/texture/MixinTextureManager.java diff --git a/fabric-textures-v0/build.gradle b/fabric-textures-v0/build.gradle index fd67659fa..2d4358b43 100644 --- a/fabric-textures-v0/build.gradle +++ b/fabric-textures-v0/build.gradle @@ -1,5 +1,5 @@ archivesBaseName = "fabric-textures-v0" -version = getSubprojectVersion(project, "0.1.0") +version = getSubprojectVersion(project, "0.1.1") dependencies { compile project(path: ':fabric-api-base', configuration: 'dev') diff --git a/fabric-textures-v0/src/main/java/net/fabricmc/fabric/api/event/client/ClientSpriteRegistryCallback.java b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/api/event/client/ClientSpriteRegistryCallback.java index d43ba4635..20aff8932 100644 --- a/fabric-textures-v0/src/main/java/net/fabricmc/fabric/api/event/client/ClientSpriteRegistryCallback.java +++ b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/api/event/client/ClientSpriteRegistryCallback.java @@ -17,32 +17,43 @@ package net.fabricmc.fabric.api.event.client; import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.minecraft.client.MinecraftClient; +import net.fabricmc.fabric.impl.client.texture.SpriteRegistryCallbackHolder; import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.SpriteAtlasTexture; import net.minecraft.util.Identifier; +import java.util.List; import java.util.Map; import java.util.function.Consumer; public interface ClientSpriteRegistryCallback { - public static final Event EVENT = EventFactory.createArrayBacked(ClientSpriteRegistryCallback.class, - (listeners) -> (atlasTexture, registry) -> { - for (ClientSpriteRegistryCallback callback : listeners) { - callback.registerSprites(atlasTexture, registry); - } - } - ); + /** + * @deprecated Use the {@link ClientSpriteRegistryCallback#event(Identifier)} registration method. Since 1.14 + * started making use of multiple sprite atlases, it is unwise to register sprites to *all* of them. + */ + @Deprecated + public static final Event EVENT = SpriteRegistryCallbackHolder.EVENT_GLOBAL; void registerSprites(SpriteAtlasTexture atlasTexture, Registry registry); + /** + * Get an event instance for a given atlas path. + * + * @param atlasId The atlas texture ID you want to register to. + * @return The event for a given atlas path. + * + * @since 0.1.1 + */ + static Event event(Identifier atlasId) { + return SpriteRegistryCallbackHolder.eventLocal(atlasId); + } + + /** + * @deprecated Use the {@link ClientSpriteRegistryCallback#event(Identifier)} registration method. + */ + @Deprecated static void registerBlockAtlas(ClientSpriteRegistryCallback callback) { - EVENT.register((atlasTexture, registry) -> { - if (atlasTexture == MinecraftClient.getInstance().getSpriteAtlas()) { - callback.registerSprites(atlasTexture, registry); - } - }); + event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register(callback); } public static class Registry { @@ -69,7 +80,7 @@ public interface ClientSpriteRegistryCallback { * @param sprite The sprite to be added. */ public void register(Sprite sprite) { - this.spriteMap.put(sprite.getId(), sprite); + spriteMap.put(sprite.getId(), sprite); } } } diff --git a/fabric-textures-v0/src/main/java/net/fabricmc/fabric/impl/client/texture/SpriteAtlasTextureHooks.java b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/impl/client/texture/SpriteAtlasTextureHooks.java new file mode 100644 index 000000000..9849e643b --- /dev/null +++ b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/impl/client/texture/SpriteAtlasTextureHooks.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.texture; + +import net.minecraft.util.Identifier; + +public interface SpriteAtlasTextureHooks { + void onRegisteredAs(Identifier id); +} diff --git a/fabric-textures-v0/src/main/java/net/fabricmc/fabric/impl/client/texture/SpriteRegistryCallbackHolder.java b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/impl/client/texture/SpriteRegistryCallbackHolder.java new file mode 100644 index 000000000..7feafa6e4 --- /dev/null +++ b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/impl/client/texture/SpriteRegistryCallbackHolder.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.texture; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback; +import net.minecraft.util.Identifier; + +import java.util.HashMap; +import java.util.Map; + +public final class SpriteRegistryCallbackHolder { + public static final Event EVENT_GLOBAL = createEvent(); + private static final Map> eventMap = new HashMap<>(); + + private SpriteRegistryCallbackHolder() { + + } + + public static Event eventLocal(Identifier key) { + return eventMap.computeIfAbsent(key, (a) -> createEvent()); + } + + private static Event createEvent() { + return EventFactory.createArrayBacked(ClientSpriteRegistryCallback.class, + (listeners) -> (atlasTexture, registry) -> { + for (ClientSpriteRegistryCallback callback : listeners) { + callback.registerSprites(atlasTexture, registry); + } + } + ); + } +} diff --git a/fabric-textures-v0/src/main/java/net/fabricmc/fabric/mixin/client/texture/MixinSpriteAtlasTexture.java b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/mixin/client/texture/MixinSpriteAtlasTexture.java index 592c0fa77..8d2587e63 100644 --- a/fabric-textures-v0/src/main/java/net/fabricmc/fabric/mixin/client/texture/MixinSpriteAtlasTexture.java +++ b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/mixin/client/texture/MixinSpriteAtlasTexture.java @@ -20,6 +20,8 @@ import com.google.common.base.Joiner; import net.fabricmc.fabric.api.client.texture.*; import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback; import net.fabricmc.fabric.impl.client.texture.FabricSprite; +import net.fabricmc.fabric.impl.client.texture.SpriteAtlasTextureHooks; +import net.fabricmc.fabric.impl.client.texture.SpriteRegistryCallbackHolder; import net.minecraft.client.resource.metadata.AnimationResourceMetadata; import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.SpriteAtlasTexture; @@ -42,7 +44,7 @@ import java.io.IOException; import java.util.*; @Mixin(SpriteAtlasTexture.class) -public abstract class MixinSpriteAtlasTexture { +public abstract class MixinSpriteAtlasTexture implements SpriteAtlasTextureHooks { @Shadow private static Logger LOGGER; @Shadow @@ -51,33 +53,38 @@ public abstract class MixinSpriteAtlasTexture { @Shadow public abstract Sprite getSprite(Identifier id); + private final Set fabric_localIds = new HashSet<>(); + + // EVENT/HOOKS LOGIC + + @Override + public void onRegisteredAs(Identifier id) { + fabric_localIds.add(id); + } + + // INJECTION LOGIC + private Map fabric_injectedSprites; - /** - * The purpose of this patch is to allow injecting sprites at the stage of Sprite instantiation, such as - * Sprites with CustomSpriteLoaders. - *

- * FabricSprite is a red herring. It's only use to go around Sprite's constructors being protected. - *

- * method_18160 is a lambda used in runAsync. - */ - @SuppressWarnings("JavaDoc") - - @Redirect(method = "method_18160", at = @At(value = "NEW", target = "net/minecraft/client/texture/Sprite")) - public Sprite newSprite(Identifier id, PngFile pngFile, AnimationResourceMetadata animationMetadata) { - if (fabric_injectedSprites.containsKey(id)) { - return fabric_injectedSprites.get(id); - } else { - return new FabricSprite(id, pngFile, animationMetadata); + // Loads in custom sprite object injections. + @Inject(at = @At("RETURN"), method = "loadSprites") + private void afterLoadSprites(ResourceManager resourceManager_1, Set set_1, CallbackInfoReturnable> info) { + if (fabric_injectedSprites != null) { + info.getReturnValue().addAll(fabric_injectedSprites.values()); + fabric_injectedSprites = null; } } + // Handles DependentSprite + custom sprite object injections. @ModifyVariable(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/SpriteAtlasTexture;loadSprites(Lnet/minecraft/resource/ResourceManager;Ljava/util/Set;)Ljava/util/Collection;"), method = "stitch") - public Set setHook(Set set) { + public Set beforeSpriteLoad(Set set) { fabric_injectedSprites = new HashMap<>(); ClientSpriteRegistryCallback.Registry registry = new ClientSpriteRegistryCallback.Registry(fabric_injectedSprites, set::add); //noinspection ConstantConditions - ClientSpriteRegistryCallback.EVENT.invoker().registerSprites((SpriteAtlasTexture) (Object) this, registry); + for (Identifier id : fabric_localIds) { + SpriteRegistryCallbackHolder.eventLocal(id).invoker().registerSprites((SpriteAtlasTexture) (Object) this, registry); + } + SpriteRegistryCallbackHolder.EVENT_GLOBAL.invoker().registerSprites((SpriteAtlasTexture) (Object) this, registry); // TODO: Unoptimized. Set dependentSprites = new HashSet<>(); @@ -90,8 +97,14 @@ public abstract class MixinSpriteAtlasTexture { } } + Set result = set; + boolean isResultNew = false; + if (!dependentSprites.isEmpty()) { - Set result = new LinkedHashSet<>(); + if (!isResultNew) { + result = new LinkedHashSet<>(); + isResultNew = true; + } for (Identifier id : set) { if (!dependentSpriteIds.contains(id)) { @@ -123,13 +136,23 @@ public abstract class MixinSpriteAtlasTexture { } throw new CrashException(report); } - - return result; - } else { - return set; } + + if (!fabric_injectedSprites.isEmpty()) { + if (!isResultNew) { + result = new LinkedHashSet<>(set); + isResultNew = true; + } + + result.removeAll(fabric_injectedSprites.keySet()); + } + + return result; } + /** + * Handles CustomSpriteLoader. + */ @Inject(at = @At("HEAD"), method = "loadSprite", cancellable = true) public void loadSprite(ResourceManager manager, Sprite sprite, CallbackInfoReturnable info) { // refer SpriteAtlasTexture.loadSprite @@ -137,24 +160,20 @@ public abstract class MixinSpriteAtlasTexture { try { if (!((CustomSpriteLoader) sprite).load(manager, mipLevel)) { info.setReturnValue(false); - info.cancel(); return; } } catch (RuntimeException | IOException e) { LOGGER.error("Unable to load custom sprite {}: {}", sprite.getId(), e); info.setReturnValue(false); - info.cancel(); return; } try { sprite.generateMipmaps(this.mipLevel); info.setReturnValue(true); - info.cancel(); } catch (Throwable e) { LOGGER.error("Unable to apply mipmap to custom sprite {}: {}", sprite.getId(), e); info.setReturnValue(false); - info.cancel(); } } } diff --git a/fabric-textures-v0/src/main/java/net/fabricmc/fabric/mixin/client/texture/MixinTextureManager.java b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/mixin/client/texture/MixinTextureManager.java new file mode 100644 index 000000000..86c965ba5 --- /dev/null +++ b/fabric-textures-v0/src/main/java/net/fabricmc/fabric/mixin/client/texture/MixinTextureManager.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.texture; + +import net.fabricmc.fabric.impl.client.texture.SpriteAtlasTextureHooks; +import net.minecraft.client.texture.Texture; +import net.minecraft.client.texture.TextureManager; +import net.minecraft.util.Identifier; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(TextureManager.class) +public class MixinTextureManager { + @Inject(at = @At("RETURN"), method = "registerTexture") + private void afterRegisterTexture(Identifier identifier, Texture texture, CallbackInfoReturnable info) { + if (texture instanceof SpriteAtlasTextureHooks) { + ((SpriteAtlasTextureHooks) texture).onRegisteredAs(identifier); + } + } +} diff --git a/fabric-textures-v0/src/main/resources/fabric-textures-v0.mixins.json b/fabric-textures-v0/src/main/resources/fabric-textures-v0.mixins.json index e9dfaac01..21468685b 100644 --- a/fabric-textures-v0/src/main/resources/fabric-textures-v0.mixins.json +++ b/fabric-textures-v0/src/main/resources/fabric-textures-v0.mixins.json @@ -3,7 +3,8 @@ "package": "net.fabricmc.fabric.mixin.client.texture", "compatibilityLevel": "JAVA_8", "client": [ - "MixinSpriteAtlasTexture" + "MixinSpriteAtlasTexture", + "MixinTextureManager" ], "injectors": { "defaultRequire": 1