Skip to content

Commit

Permalink
Expose API for dynamic sprite sources
Browse files Browse the repository at this point in the history
This does not currently compile on forge. Why? IDK. I broke the mixin annotation processor somehow.
  • Loading branch information
lukebemish committed Jul 29, 2023
1 parent 25f9ee7 commit 1291dfe
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
/*
* Copyright (C) 2023 Luke Bemish and contributors
* SPDX-License-Identifier: LGPL-3.0-or-later
*/

package dev.lukebemish.dynamicassetgenerator.impl.client;
package dev.lukebemish.dynamicassetgenerator.api.client;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dev.lukebemish.dynamicassetgenerator.api.ResourceGenerationContext;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.TexSource;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.TexSourceDataHolder;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.TextureMetaGenerator;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.TouchedTextureTracker;
import dev.lukebemish.dynamicassetgenerator.impl.DynamicAssetGenerator;
import dev.lukebemish.dynamicassetgenerator.impl.client.ExposesName;
import dev.lukebemish.dynamicassetgenerator.impl.client.ForegroundExtractor;
import dev.lukebemish.dynamicassetgenerator.impl.client.TexSourceCache;
import dev.lukebemish.dynamicassetgenerator.impl.client.platform.ClientServices;
import dev.lukebemish.dynamicassetgenerator.mixin.SpriteSourcesAccessor;
import net.minecraft.client.renderer.texture.SpriteContents;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
Expand All @@ -36,22 +32,63 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

/**
* A sprite source which makes use of {@link TexSource}s to provide sprites at resource pack load. May be more reliable
* than a {@link AssetResourceCache} for generating sprites based off of textures added by other mods which use runtime
* resource generation techniques.
*/
public interface DynamicSpriteSource extends SpriteSource {
/**
* @return a map of texture location, not including the {@code "textures/"} prefix or file extension, to texture source
*/
Map<ResourceLocation, TexSource> getSources(ResourceGenerationContext context, ResourceManager resourceManager);

public record TexSourceSpriteSource(Map<ResourceLocation, TexSource> sources, @Nullable ResourceLocation location) implements SpriteSource {
public static final ResourceLocation LOCATION = new ResourceLocation(DynamicAssetGenerator.MOD_ID, "tex_sources");
public static Codec<TexSourceSpriteSource> CODEC = RecordCodecBuilder.create(i -> i.group(
Codec.unboundedMap(ResourceLocation.CODEC, TexSource.CODEC).fieldOf("sources").forGetter(TexSourceSpriteSource::sources),
ResourceLocation.CODEC.optionalFieldOf("location").forGetter(s -> Optional.ofNullable(s.location()))
).apply(i, (sources, location) -> new TexSourceSpriteSource(sources, location.orElse(null))));
/**
* @return a unique identifier for this sprite source type
*/
ResourceLocation getLocation();

/**
* Registers a sprite source type.
* @param location the location which this sprite source type can be referenced from a texture atlas JSON file with
* @param codec a codec to provide instances of the type
*/
static void register(ResourceLocation location, Codec<? extends DynamicSpriteSource> codec) {
ClientServices.PLATFORM_CLIENT.addSpriteSource(location, codec);
}

/**
* Registers a sprite source type
* @param location the location which this sprite source type can be referenced from a texture atlas JSON file with
* @param constructor supplies instances of this sprite source type
*/
static void register(ResourceLocation location, Supplier<? extends DynamicSpriteSource> constructor) {
ClientServices.PLATFORM_CLIENT.addSpriteSource(location, Codec.unit(constructor));
}

/**
* Will be run before generation starts. Allows for clearing of anything that saves state (caches or the like).
* Implementations should call the super method to clear texture source and palette transfer caches.
* @param context context for the generation that will occur after this source is reset
*/
default void reset(ResourceGenerationContext context) {
TexSourceCache.reset(context);
ForegroundExtractor.reset(context);
}

@Override
public void run(ResourceManager resourceManager, Output output) {
default void run(ResourceManager resourceManager, SpriteSource.Output output) {
ResourceGenerationContext context = new ResourceGenerationContext() {
@Override
public @NotNull ResourceLocation getCacheName() {
return new ResourceLocation(DynamicAssetGenerator.MOD_ID, "sprite_source");
if (output instanceof ExposesName exposesName) {
var atlasName = exposesName.dynamicassetgenerator$getName();
return getLocation().withPath(getLocation().getPath()+"__"+atlasName.getNamespace()+"__"+atlasName.getPath());
}
return getLocation();
}

@Override
Expand All @@ -71,22 +108,9 @@ public void listResources(@NotNull String namespace, @NotNull String path, PackR
}
};

if (location != null) {
resourceManager.listResources(location.getNamespace() + "/" + location.getPath(), rl -> rl.getPath().endsWith(".json")).forEach((rl, resource) -> {
try (var reader = resource.openAsReader()) {
JsonElement json = DynamicAssetGenerator.GSON.fromJson(reader, JsonElement.class);
var result = TexSource.CODEC.parse(JsonOps.INSTANCE, json);
result.result().ifPresent(texSource -> {
ResourceLocation sourceLocation = new ResourceLocation(rl.getNamespace(), rl.getPath().substring(0, rl.getPath().length() - 5));
sources.put(sourceLocation, texSource);
});
result.error().ifPresent(partial ->
DynamicAssetGenerator.LOGGER.error("Failed to load tex source json for " + location + ": " + rl + ": " + partial.message()));
} catch (IOException e) {
DynamicAssetGenerator.LOGGER.error("Failed to load tex source json for " + location + ": " + rl, e);
}
});
}
this.reset(context);

Map<ResourceLocation, TexSource> sources = getSources(context, resourceManager);

sources.forEach((rl, texSource) -> {
var dataHolder = new TexSourceDataHolder();
Expand All @@ -111,7 +135,7 @@ public void listResources(@NotNull String namespace, @NotNull String path, PackR
section = AnimationMetadataSection.SERIALIZER.fromJson(animation);
}
} catch (IOException | JsonParseException e) {
DynamicAssetGenerator.LOGGER.warn("Failed to generate texture meta for sprite source "+location()+" at "+rl+": ", e);
DynamicAssetGenerator.LOGGER.warn("Failed to generate texture meta for sprite source type "+getLocation()+" at "+rl+": ", e);
}
}
}
Expand All @@ -121,7 +145,7 @@ public void listResources(@NotNull String namespace, @NotNull String path, PackR
}
return new SpriteContents(rl, frameSize, image, section);
} catch (IOException e) {
DynamicAssetGenerator.LOGGER.error("Failed to generate texture for sprite source "+location()+" at "+rl+": ", e);
DynamicAssetGenerator.LOGGER.error("Failed to generate texture for sprite source type "+getLocation()+" at "+rl+": ", e);
return null;
}
});
Expand All @@ -130,7 +154,7 @@ public void listResources(@NotNull String namespace, @NotNull String path, PackR

@Override
@NotNull
public SpriteSourceType type() {
return SpriteSourcesAccessor.getTypes().get(LOCATION);
default SpriteSourceType type() {
return SpriteSourcesAccessor.getTypes().get(getLocation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2023 Luke Bemish and contributors
* SPDX-License-Identifier: LGPL-3.0-or-later
*/

package dev.lukebemish.dynamicassetgenerator.impl.client;

import com.google.gson.JsonElement;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dev.lukebemish.dynamicassetgenerator.api.ResourceGenerationContext;
import dev.lukebemish.dynamicassetgenerator.api.client.DynamicSpriteSource;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.TexSource;
import dev.lukebemish.dynamicassetgenerator.impl.DynamicAssetGenerator;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public record BuiltinDynamicSpriteSource(Map<ResourceLocation, TexSource> sources, @Nullable ResourceLocation location) implements DynamicSpriteSource {
public static final ResourceLocation LOCATION = new ResourceLocation(DynamicAssetGenerator.MOD_ID, "tex_sources");
public static Codec<BuiltinDynamicSpriteSource> CODEC = RecordCodecBuilder.create(i -> i.group(
Codec.unboundedMap(ResourceLocation.CODEC, TexSource.CODEC).fieldOf("sources").forGetter(BuiltinDynamicSpriteSource::sources),
ResourceLocation.CODEC.optionalFieldOf("location").forGetter(s -> Optional.ofNullable(s.location()))
).apply(i, (sources, location) -> new BuiltinDynamicSpriteSource(sources, location.orElse(null))));

@Override
public Map<ResourceLocation, TexSource> getSources(ResourceGenerationContext context, ResourceManager resourceManager) {
Map<ResourceLocation, TexSource> outSources = new HashMap<>(sources());
if (location != null) {
resourceManager.listResources(location.getNamespace() + "/" + location.getPath(), rl -> rl.getPath().endsWith(".json")).forEach((rl, resource) -> {
try (var reader = resource.openAsReader()) {
JsonElement json = DynamicAssetGenerator.GSON.fromJson(reader, JsonElement.class);
var result = TexSource.CODEC.parse(JsonOps.INSTANCE, json);
result.result().ifPresent(texSource -> {
ResourceLocation sourceLocation = new ResourceLocation(rl.getNamespace(), rl.getPath().substring(0, rl.getPath().length() - 5));
outSources.put(sourceLocation, texSource);
});
result.error().ifPresent(partial ->
DynamicAssetGenerator.LOGGER.error("Failed to load tex source json for " + location + ": " + rl + ": " + partial.message()));
} catch (IOException e) {
DynamicAssetGenerator.LOGGER.error("Failed to load tex source json for " + location + ": " + rl, e);
}
});
}
return outSources;
}

@Override
public ResourceLocation getLocation() {
return LOCATION;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
import dev.lukebemish.dynamicassetgenerator.api.ResourceCache;
import dev.lukebemish.dynamicassetgenerator.api.ResourceGenerator;
import dev.lukebemish.dynamicassetgenerator.api.client.AssetResourceCache;
import dev.lukebemish.dynamicassetgenerator.api.client.DynamicSpriteSource;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.TexSource;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.TextureGenerator;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.TextureMetaGenerator;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.texsources.*;
import dev.lukebemish.dynamicassetgenerator.api.client.generators.texsources.mask.*;
import dev.lukebemish.dynamicassetgenerator.impl.DynamicAssetGenerator;
import dev.lukebemish.dynamicassetgenerator.mixin.SpriteSourcesAccessor;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.repository.Pack;

Expand Down Expand Up @@ -54,11 +54,9 @@ public static void init() {
TexSource.register(new ResourceLocation(DynamicAssetGenerator.MOD_ID, "mask/multiply"), MultiplyMask.CODEC);
TexSource.register(new ResourceLocation(DynamicAssetGenerator.MOD_ID, "mask/channel"), ChannelMask.CODEC);

testing();
}
DynamicSpriteSource.register(BuiltinDynamicSpriteSource.LOCATION, BuiltinDynamicSpriteSource.CODEC);

public static void setup() {
SpriteSourcesAccessor.invokeRegister(TexSourceSpriteSource.LOCATION.toString(), TexSourceSpriteSource.CODEC);
testing();
}

private static void testing() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.lukebemish.dynamicassetgenerator.impl.client;

import net.minecraft.resources.ResourceLocation;

public interface ExposesName {
ResourceLocation dynamicassetgenerator$getName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.lukebemish.dynamicassetgenerator.impl.client.platform;

import dev.lukebemish.dynamicassetgenerator.impl.platform.Services;

public class ClientServices {
public static final PlatformClient PLATFORM_CLIENT = Services.load(PlatformClient.class);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.lukebemish.dynamicassetgenerator.impl.client.platform;

import com.mojang.serialization.Codec;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.resources.ResourceLocation;

public interface PlatformClient {
void addSpriteSource(ResourceLocation location, Codec<? extends SpriteSource> codec);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@ApiStatus.Internal
package dev.lukebemish.dynamicassetgenerator.impl.client.platform;

import org.jetbrains.annotations.ApiStatus;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.lukebemish.dynamicassetgenerator.mixin;

import dev.lukebemish.dynamicassetgenerator.impl.client.ExposesName;
import net.minecraft.client.renderer.texture.atlas.SpriteResourceLoader;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(SpriteResourceLoader.class)
public class SpriteResourceLoaderMixin implements ExposesName {
@Unique
private ResourceLocation name;

@SuppressWarnings("DataFlowIssue")
@Inject(method = "load", at = @At("RETURN"))
private static void dynamicassetgenerator$load(ResourceManager pResourceManager, ResourceLocation pLocation, CallbackInfoReturnable<SpriteResourceLoader> cir) {
((SpriteResourceLoaderMixin) (Object) cir.getReturnValue()).name = pLocation;
}

@Override
public ResourceLocation dynamicassetgenerator$getName() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.lukebemish.dynamicassetgenerator.mixin;

import dev.lukebemish.dynamicassetgenerator.impl.client.ExposesName;
import net.minecraft.client.renderer.texture.atlas.SpriteResourceLoader;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

@Mixin(targets = "net.minecraft.client.renderer.texture.atlas.SpriteResourceLoader$1")
public class SpriteResourceLoaderOutputMixin implements ExposesName {

@Shadow(aliases = {"field_41390", "f_260614_"})
@Final
private SpriteResourceLoader this$0;

@Override
public ResourceLocation dynamicassetgenerator$getName() {
return ((ExposesName) this$0).dynamicassetgenerator$getName();
}
}
4 changes: 3 additions & 1 deletion Common/src/main/resources/mixin.dynamic_asset_generator.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
],
"client": [
"MinecraftMixin",
"SpriteSourcesAccessor"
"SpriteSourcesAccessor",
"SpriteResourceLoaderOutputMixin",
"SpriteResourceLoaderMixin"
],
"server": [],
"injectors": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLEnvironment;

Expand All @@ -22,13 +21,9 @@ public DynamicAssetGeneratorForge() {
if (FMLEnvironment.dist == Dist.CLIENT) {
DynamicAssetGeneratorClient.init();
}
modbus.addListener(this::constructModEvent);
modbus.addListener(EventHandler::addResourcePack);
}

private void constructModEvent(FMLConstructModEvent event) {
if (FMLEnvironment.dist == Dist.CLIENT) {
event.enqueueWork(DynamicAssetGeneratorClient::setup);
modbus.addListener(PlatformClientImpl::reloadListenerListener);
}
modbus.addListener(EventHandler::addResourcePack);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dev.lukebemish.dynamicassetgenerator.forge;

import com.google.auto.service.AutoService;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import dev.lukebemish.dynamicassetgenerator.impl.client.platform.PlatformClient;
import dev.lukebemish.dynamicassetgenerator.mixin.SpriteSourcesAccessor;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;

import java.util.ArrayList;
import java.util.List;

@AutoService(PlatformClient.class)
public class PlatformClientImpl implements PlatformClient {
private static final List<Pair<ResourceLocation, Codec<? extends SpriteSource>>> SPRITE_SOURCE_QUEUE = new ArrayList<>();
private static boolean SPRITE_SOURCES_REGISTERED = false;


public void addSpriteSource(ResourceLocation location, Codec<? extends SpriteSource> codec) {
if (SPRITE_SOURCES_REGISTERED) {
throw new IllegalStateException("Sprite sources have already been registered. Try registering yours during mod initialization!");
}
SPRITE_SOURCE_QUEUE.add(Pair.of(location, codec));
}

public static void reloadListenerListener(RegisterClientReloadListenersEvent event) {
if (SPRITE_SOURCES_REGISTERED) return;
SPRITE_SOURCES_REGISTERED = true;
for (var pair : SPRITE_SOURCE_QUEUE) {
SpriteSourcesAccessor.invokeRegister(pair.getFirst().toString(), pair.getSecond());
}
}
}
Loading

0 comments on commit 1291dfe

Please sign in to comment.