package com.tiedup.remake.client.gltf;
import com.tiedup.remake.client.animation.context.ContextAnimationFactory;
import com.tiedup.remake.client.animation.context.ContextGlbRegistry;
import com.tiedup.remake.v2.bondage.client.V2BondageRenderLayer;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.EntityRenderersEvent;
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Forge event registration for the glTF pipeline.
* Registers render layers and animation factory.
*/
public final class GltfClientSetup {
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
private GltfClientSetup() {}
/**
* MOD bus event subscribers (FMLClientSetupEvent, AddLayers).
*/
@Mod.EventBusSubscriber(
modid = "tiedup",
bus = Mod.EventBusSubscriber.Bus.MOD,
value = Dist.CLIENT
)
public static class ModBusEvents {
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent event) {
event.enqueueWork(() -> {
GltfCache.init();
GltfAnimationApplier.init();
LOGGER.info("[GltfPipeline] Client setup complete");
});
}
@SuppressWarnings("unchecked")
@SubscribeEvent
public static void onAddLayers(EntityRenderersEvent.AddLayers event) {
var defaultRenderer = event.getSkin("default");
if (defaultRenderer instanceof PlayerRenderer playerRenderer) {
playerRenderer.addLayer(
new V2BondageRenderLayer<>(playerRenderer)
);
LOGGER.info(
"[GltfPipeline] Render layers added to 'default' player renderer"
);
}
// Add V2 layer to slim player renderer (Alex)
var slimRenderer = event.getSkin("slim");
if (slimRenderer instanceof PlayerRenderer playerRenderer) {
playerRenderer.addLayer(
new V2BondageRenderLayer<>(playerRenderer)
);
LOGGER.info(
"[GltfPipeline] Render layers added to 'slim' player renderer"
);
}
}
/**
* Register resource reload listeners in the order required by the
* cache/consumer dependency graph.
*
*
ORDER MATTERS — do not rearrange without checking the
* invariants below. Forge does not guarantee parallel-safe
* ordering between listeners registered on the same event; we rely
* on {@code apply()} running sequentially in the order of
* {@code registerReloadListener} calls.
*
*
* - GLB cache clear (inline listener below) — must run
* first. Inside this single listener's {@code apply()}:
*
* - Blow away the raw GLB byte caches
* ({@code GltfCache.clearCache},
* {@code GltfAnimationApplier.invalidateCache},
* {@code GltfMeshRenderer.clearRenderTypeCache}).
* These three caches are mutually independent — none
* of them reads from the others — so their relative
* order is not load-bearing.
* - Reload {@code ContextGlbRegistry} from the new
* resource packs before clearing
* {@code ContextAnimationFactory.clearCache()} — if
* the order is swapped, the next factory lookup will
* lazily rebuild clips against the stale registry
* (which is still populated at that moment), cache
* them, and keep serving old data until the next
* reload.
* - Clear {@code FurnitureGltfCache} last, after the GLB
* layer has repopulated its registry but before any
* downstream item listener queries furniture models.
*
*
* - Data-driven item reload
* ({@code DataDrivenItemReloadListener}) — consumes the
* reloaded GLB registry indirectly via item JSON references.
* Must run after the GLB cache clear so any item that
* reaches into the GLB layer during load picks up fresh data.
* - GLB validation
* ({@code GlbValidationReloadListener}) — runs last. It walks
* both the item registry and the GLB cache to surface
* authoring issues via toast. If it ran earlier, missing
* items would falsely trip the "referenced but not found"
* diagnostic.
*
*
* When adding a new listener: decide where it sits in this
* producer/consumer chain. If you're not sure, add it at the end
* (the safest position — the rest of the graph is already built).
*/
@SubscribeEvent
public static void onRegisterReloadListeners(
RegisterClientReloadListenersEvent event
) {
event.registerReloadListener(
new SimplePreparableReloadListener() {
@Override
protected Void prepare(
ResourceManager resourceManager,
ProfilerFiller profiler
) {
return null;
}
@Override
protected void apply(
Void nothing,
ResourceManager resourceManager,
ProfilerFiller profiler
) {
GltfCache.clearCache();
GltfAnimationApplier.invalidateCache();
GltfMeshRenderer.clearRenderTypeCache();
// Reload context GLB animations from resource packs FIRST,
// then clear the factory cache so it rebuilds against the
// new GLB registry (prevents stale JSON fallback caching).
ContextGlbRegistry.reload(resourceManager);
ContextAnimationFactory.clearCache();
com.tiedup.remake.v2.furniture.client.FurnitureGltfCache.clear();
LOGGER.info(
"[GltfPipeline] Caches cleared on resource reload"
);
}
}
);
LOGGER.info("[GltfPipeline] Resource reload listener registered");
// Data-driven bondage item definitions (tiedup_items/*.json)
event.registerReloadListener(new DataDrivenItemReloadListener());
LOGGER.info(
"[GltfPipeline] Data-driven item reload listener registered"
);
// GLB structural validation (runs after item definitions are loaded)
event.registerReloadListener(new com.tiedup.remake.client.gltf.diagnostic.GlbValidationReloadListener());
LOGGER.info("[GltfPipeline] GLB validation reload listener registered");
}
}
/** FORGE bus event subscribers (client-side commands). */
@Mod.EventBusSubscriber(
modid = "tiedup",
bus = Mod.EventBusSubscriber.Bus.FORGE,
value = Dist.CLIENT
)
public static class ForgeBusEvents {
@SubscribeEvent
public static void onRegisterClientCommands(
net.minecraftforge.client.event.RegisterClientCommandsEvent event
) {
com.tiedup.remake.commands.ValidateGlbCommand.register(
event.getDispatcher()
);
LOGGER.info(
"[GltfPipeline] Client command /tiedup validate registered"
);
}
}
}