From bdffabc08e2eb9895f966c949acc8334a2bf4475 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 13 Nov 2024 10:19:10 +0000 Subject: [PATCH] Clarify docs around registering peripherals --- projects/common-api/build.gradle.kts | 30 +++ .../api/network/wired/WiredElement.java | 2 +- .../shared/computer/apis/CommandAPI.java | 2 +- .../data/HasComputerIdLootCondition.java | 2 +- .../computercraft/api/lua/GenericSource.java | 2 +- .../computercraft/api/package-info.java | 4 +- .../api/peripheral/GenericPeripheral.java | 14 +- .../api/peripheral/IDynamicPeripheral.java | 1 - .../api/peripheral/IPeripheral.java | 24 +- .../api/peripheral/PeripheralType.java | 2 + .../api/peripheral/WorkMonitor.java | 2 +- .../api/peripheral/package-info.java | 205 ++++++++++++++++++ .../mainthread/MainThreadExecutor.java | 2 +- 13 files changed, 270 insertions(+), 22 deletions(-) create mode 100644 projects/core-api/src/main/java/dan200/computercraft/api/peripheral/package-info.java diff --git a/projects/common-api/build.gradle.kts b/projects/common-api/build.gradle.kts index 3bdc0d282..b00eb56db 100644 --- a/projects/common-api/build.gradle.kts +++ b/projects/common-api/build.gradle.kts @@ -8,6 +8,8 @@ plugins { id("cc-tweaked.vanilla") } +val mcVersion: String by extra + java { withJavadocJar() } @@ -17,8 +19,36 @@ dependencies { } tasks.javadoc { + title = "CC: Tweaked $version Minecraft $mcVersion" include("dan200/computercraft/api/**/*.java") + options { + (this as StandardJavadocDocletOptions) + + groups = mapOf( + "Common" to listOf( + "dan200.computercraft.api", + "dan200.computercraft.api.lua", + "dan200.computercraft.api.peripheral", + ), + "Upgrades" to listOf( + "dan200.computercraft.api.client.turtle", + "dan200.computercraft.api.pocket", + "dan200.computercraft.api.turtle", + "dan200.computercraft.api.upgrades", + ), + ) + + addBooleanOption("-allow-script-in-comments", true) + bottom( + """ + + + + """.trimIndent(), + ) + } + // Include the core-api in our javadoc export. This is wrong, but it means we can export a single javadoc dump. source(project(":core-api").sourceSets.main.map { it.allJava }) } diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/network/wired/WiredElement.java b/projects/common-api/src/main/java/dan200/computercraft/api/network/wired/WiredElement.java index 0d58975f3..01d197ebd 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/network/wired/WiredElement.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/network/wired/WiredElement.java @@ -14,7 +14,7 @@ import dan200.computercraft.api.ComputerCraftAPI; * as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant * for its lifespan. *

- * Elements are generally tied to a block or tile entity in world. In such as case, one should provide the + * Elements are generally tied to a block or block entity in world. In such as case, one should provide the * {@link WiredElement} capability for the appropriate sides. */ public interface WiredElement extends WiredSender { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java index b9574340b..10b937237 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -248,7 +248,7 @@ public class CommandAPI implements ILuaAPI { * Get some basic information about a block. *

* The returned table contains the current name, metadata and block state (as - * with [`turtle.inspect`]). If there is a tile entity for that block, its NBT + * with [`turtle.inspect`]). If there is a block entity for that block, its NBT * will also be returned. * * @param x The x position of the block to query. diff --git a/projects/common/src/main/java/dan200/computercraft/shared/data/HasComputerIdLootCondition.java b/projects/common/src/main/java/dan200/computercraft/shared/data/HasComputerIdLootCondition.java index cb1f4f319..63c328c92 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/data/HasComputerIdLootCondition.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/data/HasComputerIdLootCondition.java @@ -15,7 +15,7 @@ import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; import java.util.Set; /** - * A loot condition which checks if the tile entity has a non-0 ID. + * A loot condition which checks if the block entity has a computer ID. */ public final class HasComputerIdLootCondition implements LootItemCondition { public static final HasComputerIdLootCondition INSTANCE = new HasComputerIdLootCondition(); diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/lua/GenericSource.java b/projects/core-api/src/main/java/dan200/computercraft/api/lua/GenericSource.java index 515967ed7..99c8493bf 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/lua/GenericSource.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/lua/GenericSource.java @@ -23,7 +23,7 @@ import dan200.computercraft.api.peripheral.IPeripheral; * *

{@code
  * public class InventoryMethods implements GenericSource {
- *     \@LuaFunction( mainThread = true )
+ *     \@LuaFunction(mainThread = true)
  *     public int size(IItemHandler inventory) {
  *         return inventory.getSlots();
  *     }
diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/package-info.java b/projects/core-api/src/main/java/dan200/computercraft/api/package-info.java
index a19a3583d..9d15df9be 100644
--- a/projects/core-api/src/main/java/dan200/computercraft/api/package-info.java
+++ b/projects/core-api/src/main/java/dan200/computercraft/api/package-info.java
@@ -7,11 +7,13 @@
  * 

* You probably want to start in the following places: *

*/ @DefaultQualifier(value = NonNull.class, locations = { diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/GenericPeripheral.java b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/GenericPeripheral.java index bb866976e..e288ee449 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/GenericPeripheral.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/GenericPeripheral.java @@ -19,17 +19,17 @@ public interface GenericPeripheral extends GenericSource { * Unlike normal {@link IPeripheral}s, {@link GenericPeripheral} do not have to have a type. By default, the * resulting peripheral uses the resource name of the wrapped block entity (for instance {@code minecraft:chest}). *

- * However, in some cases it may be more appropriate to specify a more readable name. Overriding this method allows - * you to do so. + * However, in some cases it may be more appropriate to specify a more readable name, or provide + * {@linkplain PeripheralType#getAdditionalTypes() additional types}. Overriding this method allows you to do so. *

- * When multiple {@link GenericPeripheral}s return a non-empty peripheral type for a single tile entity, the - * lexicographically smallest will be chosen. In order to avoid this conflict, this method should only be - * implemented when your peripheral targets a single tile entity AND it's likely that you're the - * only mod to do so. Similarly this should NOT be implemented when your methods target a - * capability or other interface (such as Forge's {@code IItemHandler}). + * When multiple {@link GenericPeripheral}s provide a {@linkplain PeripheralType#getPrimaryType() primary peripheral + * type} for a single block entity, the lexicographically smallest will be chosen. In order to avoid this conflict, + * primary types should only be used when your peripheral targets a single block entity AND it's + * likely that you're the only mod to do so. * * @return The type of this peripheral or {@link PeripheralType#untyped()}. * @see IPeripheral#getType() + * @see IPeripheral#getAdditionalTypes() */ default PeripheralType getType() { return PeripheralType.untyped(); diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/IDynamicPeripheral.java b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/IDynamicPeripheral.java index 6d2b96c4a..230f7694f 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/IDynamicPeripheral.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/IDynamicPeripheral.java @@ -10,7 +10,6 @@ import dan200.computercraft.api.lua.*; * A peripheral whose methods are not known at runtime. *

* This behaves similarly to {@link IDynamicLuaObject}, though also accepting the current {@link IComputerAccess}. - * Generally one may use {@link LuaFunction} instead of implementing this interface. */ public interface IDynamicPeripheral extends IPeripheral { /** diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java index 3d96075a8..1b80787f0 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java @@ -4,19 +4,28 @@ package dan200.computercraft.api.peripheral; +import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.LuaFunction; +import dan200.computercraft.api.lua.LuaTask; import javax.annotation.Nullable; import java.util.Set; /** - * The interface that defines a peripheral. + * A peripheral is an external device that a computer can interact with. *

- * In order to expose a peripheral for your block or block entity, you should either attach a capability (Forge) or - * use the block lookup API (Fabric). This interface cannot be implemented directly on the block entity. + * Peripherals can be supplied by both a block (or block entity), or from + * {@linkplain dan200.computercraft.api.turtle.ITurtleUpgrade#createPeripheral(dan200.computercraft.api.turtle.ITurtleAccess, dan200.computercraft.api.turtle.TurtleSide) turtle} + * or {@linkplain dan200.computercraft.api.pocket.IPocketUpgrade#createPeripheral(dan200.computercraft.api.pocket.IPocketAccess) pocket} + * upgrades. *

- * Peripherals should provide a series of methods to the user, either using {@link LuaFunction} or by implementing - * {@link IDynamicPeripheral}. + * See the {@linkplain dan200.computercraft.api.peripheral package documentation} for more information on registering peripherals. + *

+ * Peripherals should provide a series of methods to the user, typically by annotating Java methods with + * {@link LuaFunction}. Alternatively, {@link IDynamicPeripheral} may be used to provide a dynamic set of methods. + * Remember that peripheral methods are called on the computer thread, and so it is not safe to interact with + * the Minecraft world by default. One should use {@link LuaFunction#mainThread()} or + * {@link ILuaContext#executeMainThreadTask(LuaTask)} to run code on the main server thread. */ public interface IPeripheral { /** @@ -24,6 +33,7 @@ public interface IPeripheral { * This can be queried from lua by calling {@code peripheral.getType()} * * @return A string identifying the type of peripheral. + * @see PeripheralType#getPrimaryType() */ String getType(); @@ -81,7 +91,7 @@ public interface IPeripheral { } /** - * Get the object that this peripheral provides methods for. This will generally be the tile entity + * Get the object that this peripheral provides methods for. This will generally be the block entity * or block, but may be an inventory, entity, etc... * * @return The object this peripheral targets @@ -95,7 +105,7 @@ public interface IPeripheral { * Determine whether this peripheral is equivalent to another one. *

* The minimal example should at least check whether they are the same object. However, you may wish to check if - * they point to the same block or tile entity. + * they point to the same block or block entity. * * @param other The peripheral to compare against. This may be {@code null}. * @return Whether these peripherals are equivalent. diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/PeripheralType.java b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/PeripheralType.java index 08cc97448..e651df006 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/PeripheralType.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/PeripheralType.java @@ -96,6 +96,7 @@ public final class PeripheralType { * Get the name of this peripheral type. This may be {@code null}. * * @return The type of this peripheral. + * @see IPeripheral#getType() */ @Nullable public String getPrimaryType() { @@ -107,6 +108,7 @@ public final class PeripheralType { * a peripheral might have. * * @return All additional types. + * @see IPeripheral#getAdditionalTypes() */ public Set getAdditionalTypes() { return additionalTypes; diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/WorkMonitor.java b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/WorkMonitor.java index 265285c86..49d44da1c 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/WorkMonitor.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/WorkMonitor.java @@ -11,7 +11,7 @@ import java.util.concurrent.TimeUnit; * Monitors "work" associated with a computer, keeping track of how much a computer has done, and ensuring every * computer receives a fair share of any processing time. *

- * This is primarily intended for work done by peripherals on the main thread (such as on a tile entity's tick), but + * This is primarily intended for work done by peripherals on the main thread (such as on a block entity's tick), but * could be used for other purposes (such as complex computations done on another thread). *

* Before running a task, one should call {@link #canWork()} to determine if the computer is currently allowed to diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/package-info.java b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/package-info.java new file mode 100644 index 000000000..dfc48e670 --- /dev/null +++ b/projects/core-api/src/main/java/dan200/computercraft/api/peripheral/package-info.java @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +/** + * Peripherals for blocks and upgrades. + *

+ * A peripheral is an external device that a computer can interact with. Peripherals can be supplied by both a block (or + * block entity), or from {@linkplain dan200.computercraft.api.turtle.ITurtleUpgrade#createPeripheral(dan200.computercraft.api.turtle.ITurtleAccess, dan200.computercraft.api.turtle.TurtleSide) turtle} + * or {@linkplain dan200.computercraft.api.pocket.IPocketUpgrade#createPeripheral(dan200.computercraft.api.pocket.IPocketAccess) pocket} + * upgrades. + * + *

Creating peripherals for blocks

+ * One of the most common things you'll want to do with ComputerCraft's API is register new peripherals. This is + * relatively simple once you know how to do it, but may be a bit confusing the first time round. + *

+ * There are currently two possible ways to define a peripheral in ComputerCraft: + *

+ *

+ * In the following examples, we'll write a peripheral method that returns the remaining burn time of a furnace, and + * demonstrate how to register this peripheral. + * + *

Creating a generic peripheral

+ * First, we'll need to create a new {@code final} class, that implements {@link dan200.computercraft.api.peripheral.GenericPeripheral}. + * You'll need to implement {@link dan200.computercraft.api.peripheral.GenericPeripheral#id()}, which should just return + * some namespaced-string with your mod id. + *

+ * Then, we can start adding methods to your block entity. Each method should take its target type as the first + * argument, which in this case is a {@code AbstractFurnaceBlockEntity}. We then annotate this method with + * {@link dan200.computercraft.api.lua.LuaFunction} to expose it to computers. + * + *

{@code
+ * import dan200.computercraft.api.lua.LuaFunction;
+ * import dan200.computercraft.api.peripheral.GenericPeripheral;
+ * import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
+ *
+ * public final class FurnacePeripheral implements GenericPeripheral {
+ *     @Override
+ *     public String id() {
+ *         return "mymod:furnace";
+ *     }
+ *
+ *     @LuaFunction(mainThread = true)
+ *     public int getBurnTime(AbstractFurnaceBlockEntity furnace) {
+ *         return furnace.litTime;
+ *     }
+ * }
+ * }
+ *

+ * Finally, we need to register our peripheral, so that ComputerCraft is aware of it: + * + *

{@code
+ * import dan200.computercraft.api.ComputerCraftAPI;
+ *
+ * public class ComputerCraftCompat {
+ *     public static void register() {
+ *         ComputerCraftAPI.registerGenericSource(new FurnacePeripheral());
+ *     }
+ * }
+ * }
+ * + *

Creating a {@code IPeripheral}

+ * First, we'll need to create a new class that implements {@link dan200.computercraft.api.peripheral.IPeripheral}. This + * requires a couple of boilerplate methods: one to get the type of the peripheral, and an equality function. + *

+ * We can then start adding peripheral methods to our class. Each method should be {@code final}, and annotated with + * {@link dan200.computercraft.api.lua.LuaFunction}. + * + *

{@code
+ * import dan200.computercraft.api.lua.LuaFunction;
+ * import dan200.computercraft.api.peripheral.IPeripheral;
+ * import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
+ * import org.jetbrains.annotations.Nullable;
+ *
+ * public class FurnacePeripheral implements IPeripheral {
+ *     private final AbstractFurnaceBlockEntity furnace;
+ *
+ *     public FurnacePeripheral(AbstractFurnaceBlockEntity furnace) {
+ *         this.furnace = furnace;
+ *     }
+ *
+ *     @Override
+ *     public String getType() {
+ *         return "furnace";
+ *     }
+ *
+ *     @LuaFunction(mainThread = true)
+ *     public final int getBurnTime() {
+ *         return furnace.litTime;
+ *     }
+ *
+ *     @Override
+ *     public boolean equals(@Nullable IPeripheral other) {
+ *         return this == other || other instanceof FurnacePeripheral p && furnace == p.furnace;
+ *     }
+ * }
+ * }
+ *

+ * Finally, we'll need to register our peripheral. This is done with capabilities on Forge, or the block lookup API on + * Fabric. + * + *

Registering {@code IPeripheral} on Forge

+ * Registering a peripheral on Forge can be done by attaching the {@link dan200.computercraft.api.peripheral.IPeripheral} + * to a block entity. Unfortunately, this requires quite a lot of boilerplate, due to the awkward nature of + * {@code ICapabilityProvider}. If you've got an existing system for dealing with this, we recommend you use that, + * otherwise you can use something similar to the code below: + * + *
{@code
+ * import dan200.computercraft.api.peripheral.IPeripheral;
+ * import net.minecraft.core.Direction;
+ * import net.minecraft.resources.ResourceLocation;
+ * import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
+ * import net.minecraft.world.level.block.entity.BlockEntity;
+ * import net.minecraftforge.common.capabilities.Capability;
+ * import net.minecraftforge.common.capabilities.CapabilityManager;
+ * import net.minecraftforge.common.capabilities.CapabilityToken;
+ * import net.minecraftforge.common.capabilities.ICapabilityProvider;
+ * import net.minecraftforge.common.util.LazyOptional;
+ * import net.minecraftforge.event.AttachCapabilitiesEvent;
+ * import org.jetbrains.annotations.Nullable;
+ *
+ * import java.util.function.Function;
+ *
+ * public class ComputerCraftCompat {
+ *     public static final Capability CAPABILITY_PERIPHERAL = CapabilityManager.get(new CapabilityToken<>() {
+ *     });
+ *     private static final ResourceLocation PERIPHERAL = new ResourceLocation("mymod", "peripheral");
+ *
+ *     public static void register(AttachCapabilitiesEvent event) {
+ *         if (event.getObject() instanceof AbstractFurnaceBlockEntity furnace) {
+ *             PeripheralProvider.attach(event, furnace, FurnacePeripheral::new);
+ *         }
+ *     }
+ *
+ *     // A {@link ICapabilityProvider} that lazily creates an {@link IPeripheral} when required.
+ *     private static class PeripheralProvider implements ICapabilityProvider {
+ *         private final O blockEntity;
+ *         private final Function factory;
+ *         private @Nullable LazyOptional peripheral;
+ *
+ *         private PeripheralProvider(O blockEntity, Function factory) {
+ *             this.blockEntity = blockEntity;
+ *             this.factory = factory;
+ *         }
+ *
+ *         private static  void attach(AttachCapabilitiesEvent event, O blockEntity, Function factory) {
+ *             var provider = new PeripheralProvider<>(blockEntity, factory);
+ *             event.addCapability(PERIPHERAL, provider);
+ *             event.addListener(provider::invalidate);
+ *         }
+ *
+ *         private void invalidate() {
+ *             if (peripheral != null) peripheral.invalidate();
+ *             peripheral = null;
+ *         }
+ *
+ *         @Override
+ *         public  LazyOptional getCapability(Capability capability, @Nullable Direction direction) {
+ *             if (capability != CAPABILITY_PERIPHERAL) return LazyOptional.empty();
+ *             if (blockEntity.isRemoved()) return LazyOptional.empty();
+ *
+ *             var peripheral = this.peripheral;
+ *             return (peripheral == null ? (this.peripheral = LazyOptional.of(() -> factory.apply(blockEntity))) : peripheral).cast();
+ *         }
+ *     }
+ * }
+ * }
+ * + *

Registering {@code IPeripheral} on Fabric

+ * Registering a peripheral on Fabric can be done using the block lookup API, via {@code PeripheralLookup}. + * + *
{@code
+ * import dan200.computercraft.api.peripheral.PeripheralLookup;
+ * import dan200.computercraft.example.FurnacePeripheral;
+ * import net.minecraft.world.level.block.entity.BlockEntityType;
+ *
+ * public class ComputerCraftCompat {
+ *     public static void register() {
+ *         PeripheralLookup.get().registerForBlockEntity((f, s) -> new FurnacePeripheral(f), BlockEntityType.FURNACE);
+ *         PeripheralLookup.get().registerForBlockEntity((f, s) -> new FurnacePeripheral(f), BlockEntityType.BLAST_FURNACE);
+ *         PeripheralLookup.get().registerForBlockEntity((f, s) -> new FurnacePeripheral(f), BlockEntityType.SMOKER);
+ *     }
+ * }
+ * }
+ */ +package dan200.computercraft.api.peripheral; diff --git a/projects/core/src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadExecutor.java b/projects/core/src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadExecutor.java index adf92fa7e..e72978d25 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadExecutor.java +++ b/projects/core/src/main/java/dan200/computercraft/core/computer/mainthread/MainThreadExecutor.java @@ -30,7 +30,7 @@ import java.util.concurrent.TimeUnit; *

* Now, if anywhere during this period, we use more than our allocated time slice, the executor is marked as * {@link State#HOT}. This means it will no longer be able to execute {@link MainThread} tasks (though will still - * execute tile entity tasks, in order to prevent the main thread from exhausting work every tick). + * execute block entity tasks, in order to prevent the main thread from exhausting work every tick). *

* At the beginning of the next tick, we increment the budget e by {@link MainThreadConfig#maxComputerTime()} and any * {@link State#HOT} executors are marked as {@link State#COOLING}. They will remain cooling until their budget is fully