diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/ComputerCraftAPIClient.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/ComputerCraftAPIClient.java index ac11f4dca..0d6bb52c7 100644 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/ComputerCraftAPIClient.java +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/ComputerCraftAPIClient.java @@ -27,8 +27,13 @@ public final class ComputerCraftAPIClient { * @param serialiser The turtle upgrade serialiser. * @param modeller The upgrade modeller. * @param The type of the turtle upgrade. + * @deprecated This method can lead to confusing load behaviour on Forge. Use + * {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} on Fabric, or + * {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} on Forge. */ + @Deprecated(forRemoval = true) public static void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser serialiser, TurtleUpgradeModeller modeller) { + // TODO(1.20.4): Remove this getInstance().registerTurtleUpgradeModeller(serialiser, modeller); } diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java new file mode 100644 index 000000000..fb4f6121b --- /dev/null +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleUpgradeModeller.java @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.client.turtle; + +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser; + +/** + * A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades. + *

+ * This interface is largely intended to be used from multi-loader code, to allow sharing registration code between + * multiple loaders. + */ +@FunctionalInterface +public interface RegisterTurtleUpgradeModeller { + /** + * Register a {@link TurtleUpgradeModeller}. + * + * @param serialiser The turtle upgrade serialiser. + * @param modeller The upgrade modeller. + * @param The type of the turtle upgrade. + */ + void register(TurtleUpgradeSerialiser serialiser, TurtleUpgradeModeller modeller); +} diff --git a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModeller.java b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModeller.java index c5b8bdd30..7623f7d30 100644 --- a/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModeller.java +++ b/projects/common-api/src/client/java/dan200/computercraft/api/client/turtle/TurtleUpgradeModeller.java @@ -4,12 +4,10 @@ package dan200.computercraft.api.client.turtle; -import dan200.computercraft.api.client.ComputerCraftAPIClient; import dan200.computercraft.api.client.TransformedModel; import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleSide; -import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser; import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.nbt.CompoundTag; @@ -21,9 +19,13 @@ import java.util.List; /** * Provides models for a {@link ITurtleUpgrade}. + *

+ * Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a + * modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one + * on Forge * * @param The type of turtle upgrade this modeller applies to. - * @see ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller) To register a modeller. + * @see RegisterTurtleUpgradeModeller For multi-loader registration support. */ public interface TurtleUpgradeModeller { /** diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeSerialiser.java b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeSerialiser.java index 5e6b54a3e..863f36fa0 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeSerialiser.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeSerialiser.java @@ -48,13 +48,8 @@ import java.util.function.Function; * } * } *

- * Finally, we need to register a model for our upgrade. This is done with - * {@link dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller}: - * - *

{@code
- * // Register our model inside FMLClientSetupEvent
- * ComputerCraftAPIClient.registerTurtleUpgradeModeller(MY_UPGRADE.get(), TurtleUpgradeModeller.flatItem())
- * }
+ * Finally, we need to register a model for our upgrade. The way to do this varies on mod loader, see + * {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information. *

* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files. * diff --git a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java index 490c12d6a..e4d0f9d9e 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java +++ b/projects/common/src/client/java/dan200/computercraft/client/ClientRegistry.java @@ -5,8 +5,8 @@ package dan200.computercraft.client; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.api.client.ComputerCraftAPIClient; import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; +import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller; import dan200.computercraft.client.gui.*; import dan200.computercraft.client.pocket.ClientPocketComputers; import dan200.computercraft.client.render.RenderTypes; @@ -60,18 +60,6 @@ public final class ClientRegistry { * Register any client-side objects which don't have to be done on the main thread. */ public static void register() { - ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided( - new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"), - new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right") - )); - ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided( - new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"), - new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right") - )); - ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false)); - ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true)); - ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem()); - BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), MonitorBlockEntityRenderer::new); BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new); BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new); @@ -103,6 +91,20 @@ public final class ClientRegistry { ); } + public static void registerTurtleModellers(RegisterTurtleUpgradeModeller register) { + register.register(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided( + new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"), + new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right") + )); + register.register(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided( + new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"), + new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right") + )); + register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false)); + register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true)); + register.register(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem()); + } + @SafeVarargs private static void registerItemProperty(String name, ClampedItemPropertyFunction getter, Supplier... items) { var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name); diff --git a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java index 74c5a12ec..7bff349a2 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java +++ b/projects/common/src/client/java/dan200/computercraft/client/turtle/TurtleUpgradeModellers.java @@ -11,11 +11,14 @@ import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser; +import dan200.computercraft.impl.PlatformHelper; import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.impl.UpgradeManager; import net.minecraft.client.Minecraft; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Map; import java.util.WeakHashMap; @@ -24,14 +27,15 @@ import java.util.stream.Stream; /** * A registry of {@link TurtleUpgradeModeller}s. - * - * @see dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller) */ public final class TurtleUpgradeModellers { + private static final Logger LOG = LoggerFactory.getLogger(TurtleUpgradeModellers.class); + private static final TurtleUpgradeModeller NULL_TURTLE_MODELLER = (upgrade, turtle, side) -> new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity()); private static final Map, TurtleUpgradeModeller> turtleModels = new ConcurrentHashMap<>(); + private static volatile boolean fetchedModels; /** * In order to avoid a double lookup of {@link ITurtleUpgrade} to {@link UpgradeManager.UpgradeWrapper} to @@ -45,12 +49,18 @@ public final class TurtleUpgradeModellers { } public static void register(TurtleUpgradeSerialiser serialiser, TurtleUpgradeModeller modeller) { - synchronized (turtleModels) { - if (turtleModels.containsKey(serialiser)) { - throw new IllegalStateException("Modeller already registered for serialiser"); - } + if (fetchedModels) { + // TODO(1.20.4): Replace with an error. + LOG.warn( + "Turtle upgrade serialiser {} was registered too late, its models may not be loaded correctly. If you are " + + "the mod author, you may be using a deprecated API - see https://github.com/cc-tweaked/CC-Tweaked/pull/1684 " + + "for further information.", + PlatformHelper.get().getRegistryKey(TurtleUpgradeSerialiser.registryId(), serialiser) + ); + } - turtleModels.put(serialiser, modeller); + if (turtleModels.putIfAbsent(serialiser, modeller) != null) { + throw new IllegalStateException("Modeller already registered for serialiser"); } } @@ -75,6 +85,7 @@ public final class TurtleUpgradeModellers { } public static Stream getDependencies() { + fetchedModels = true; return turtleModels.values().stream().flatMap(x -> x.getDependencies().stream()); } } diff --git a/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java b/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java new file mode 100644 index 000000000..0082aae5a --- /dev/null +++ b/projects/fabric-api/src/client/java/dan200/computercraft/api/client/FabricComputerCraftAPIClient.java @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.client; + +import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser; +import dan200.computercraft.impl.client.ComputerCraftAPIClientService; + +/** + * The Fabric-specific entrypoint for ComputerCraft's API. + * + * @see dan200.computercraft.api.ComputerCraftAPI The main API + * @see dan200.computercraft.api.client.ComputerCraftAPIClient The main client-side API + */ +public final class FabricComputerCraftAPIClient { + private FabricComputerCraftAPIClient() { + } + + /** + * Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades. + *

+ * This may be called at any point after registry creation, though it is recommended to call it within your client + * setup step. + *

+ * This method may be used as a {@link dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller}, for + * convenient use in multi-loader code. + * + * @param serialiser The turtle upgrade serialiser. + * @param modeller The upgrade modeller. + * @param The type of the turtle upgrade. + */ + public static void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser serialiser, TurtleUpgradeModeller modeller) { + getInstance().registerTurtleUpgradeModeller(serialiser, modeller); + } + + private static ComputerCraftAPIClientService getInstance() { + return ComputerCraftAPIClientService.get(); + } +} diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java b/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java index eb35b3208..530c596c8 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/ComputerCraftClient.java @@ -5,6 +5,7 @@ package dan200.computercraft.client; import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.client.FabricComputerCraftAPIClient; import dan200.computercraft.client.model.CustomModelLoader; import dan200.computercraft.impl.Services; import dan200.computercraft.shared.ModRegistry; @@ -40,6 +41,7 @@ public class ComputerCraftClient { } ClientRegistry.register(); + ClientRegistry.registerTurtleModellers(FabricComputerCraftAPIClient::registerTurtleUpgradeModeller); ClientRegistry.registerItemColours(ColorProviderRegistry.ITEM::register); ClientRegistry.registerMainThread(); diff --git a/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java b/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java new file mode 100644 index 000000000..fa306f78a --- /dev/null +++ b/projects/forge-api/src/client/java/dan200/computercraft/api/client/turtle/RegisterTurtleModellersEvent.java @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.client.turtle; + +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser; +import dan200.computercraft.api.turtle.TurtleUpgradeType; +import dan200.computercraft.impl.client.ComputerCraftAPIClientService; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.event.IModBusEvent; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; + +/** + * This event is fired to register {@link TurtleUpgradeModeller}s for a mod's {@linkplain TurtleUpgradeType turtle + * upgrades}. + *

+ * This event is fired during the initial resource load. Registries will be frozen, but mods may not be fully + * initialised at this point (i.e. {@link FMLCommonSetupEvent} or {@link FMLClientSetupEvent} may not have been + * dispatched). Subscribers should be careful not to + */ +public class RegisterTurtleModellersEvent extends Event implements IModBusEvent, RegisterTurtleUpgradeModeller { + /** + * {@inheritDoc} + */ + @Override + public void register(TurtleUpgradeSerialiser serialiser, TurtleUpgradeModeller modeller) { + ComputerCraftAPIClientService.get().registerTurtleUpgradeModeller(serialiser, modeller); + } +} diff --git a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java index 2db7c8c79..89e9ea8b2 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/ForgeClientRegistry.java @@ -5,6 +5,7 @@ package dan200.computercraft.client; import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent; import dan200.computercraft.client.model.turtle.TurtleModelLoader; import net.minecraft.client.Minecraft; import net.minecraftforge.api.distmarker.Dist; @@ -13,6 +14,7 @@ import net.minecraftforge.client.event.RegisterClientReloadListenersEvent; import net.minecraftforge.client.event.RegisterColorHandlersEvent; import net.minecraftforge.client.event.RegisterShadersEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ModLoader; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; @@ -23,6 +25,9 @@ import java.io.IOException; */ @Mod.EventBusSubscriber(modid = ComputerCraftAPI.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) public final class ForgeClientRegistry { + private static final Object lock = new Object(); + private static boolean gatheredModellers = false; + private ForgeClientRegistry() { } @@ -31,8 +36,26 @@ public final class ForgeClientRegistry { event.register("turtle", TurtleModelLoader.INSTANCE); } + /** + * Turtle upgrade modellers must be loaded before we gather additional models. + *

+ * Unfortunately, due to the nature of parallel mod loading (resource loading and mod setup events are fired in + * parallel), there's no way to guarantee this using existing events. Instead, we piggyback off + * {@link ModelEvent.RegisterAdditional}, registering models the first time the event is fired. + */ + private static void gatherModellers() { + if (gatheredModellers) return; + synchronized (lock) { + if (gatheredModellers) return; + + gatheredModellers = true; + ModLoader.get().postEvent(new RegisterTurtleModellersEvent()); + } + } + @SubscribeEvent public static void registerModels(ModelEvent.RegisterAdditional event) { + gatherModellers(); ClientRegistry.registerExtraModels(event::register); } @@ -41,6 +64,11 @@ public final class ForgeClientRegistry { ClientRegistry.registerShaders(event.getResourceProvider(), event::registerShader); } + @SubscribeEvent + public static void onTurtleModellers(RegisterTurtleModellersEvent event) { + ClientRegistry.registerTurtleModellers(event); + } + @SubscribeEvent public static void onItemColours(RegisterColorHandlersEvent.Item event) { ClientRegistry.registerItemColours(event::register);