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.

* *
    *
  1. GLB cache clear (inline listener below) — must run * first. Inside this single listener's {@code apply()}: *
      *
    1. 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.
    2. *
    3. 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.
    4. *
    5. Clear {@code FurnitureGltfCache} last, after the GLB * layer has repopulated its registry but before any * downstream item listener queries furniture models.
    6. *
    *
  2. *
  3. 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.
  4. *
  5. 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.
  6. *
* *

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" ); } } }