1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-01 22:22:59 +00:00

Compare commits

...

12 Commits

Author SHA1 Message Date
Jonathan Coates
39a5e40c92 Bump CC:T to 1.109.2
Take two!
2023-12-16 22:46:30 +00:00
Jonathan Coates
763ba51919 Update Cobalt to 0.8.1 2023-12-16 22:39:48 +00:00
Jonathan Coates
cf6ec8c28f Add a slightly cleaner system for excluding deps
Previously we prevented our published full jar depending on any of the
other projects by excluding the whole cc.tweaked jar. However, as Cobalt
also now lives in that group, this meant we were missing the Cobalt
dependency.

Rather than specifying a wildcard, we now exclude the dependencies when
adding them to the project.
2023-12-16 22:35:15 +00:00
Jonathan Coates
95d3b646b2 Bump CC:T to 1.109.1 2023-12-16 19:09:39 +00:00
Jonathan Coates
488f66eead Fix mouse_drag not firing for right/middle buttons
This is a bit of an odd combination of a few bugs:
 - When the terminal component is blurred, we fire a mouse_up event for
   the last-held button. However, we had an off-by-1 error here, so this
   only triggered for the right/middle buttons.

 - This was obsucuring the second bug, which is when we clicked within
   the terminal, this caused the terminal to be blurred (thus releasing
   the mouse) and then focused again.

   We fix this by only setting the focus if there's actually a change.

Fixes #1655
2023-12-10 12:01:34 +00:00
Jonathan Coates
1f7d245876 Specify charset when printing error messages 2023-12-08 09:52:17 +00:00
Jonathan Coates
af12b3a0ea Fix goto/:: tokens erroring in error reporting 2023-12-07 19:47:39 +00:00
Jonathan Coates
eb3e8ba677 Fix deadlock when adding/removing observers
When adding/removing observers, we locked on the observer, then
acquired the global lock. When a metric is observed, then we acquire the
global lock and then the observer lock.

If these happen at the same time, we can easily end up with a deadlock.
We simply avoid holding the observer lock for the entire add/remove
process (instead only locking when actually needed).

Closes #1639
2023-12-01 12:33:03 +00:00
Jonathan Coates
2043939531 Add compostors to the list of usable blocks
Fixes #1638
2023-11-22 18:24:59 +00:00
Jonathan Coates
84a799d27a Add abstract classes for our generic peripherals
This commit adds abstract classes to describe the interface for our
mod-loader-specific generic peripherals (inventories, fluid storage,
item storage).

This offers several advantages:
 - Javadoc to illuaminate conversion no longer needs the Forge project
   (just core and common).

 - Ensures we have a consistent interface between Forge and Fabric.

Note, this does /not/ implement fluid or energy storage for Fabric. We
probably could do fluid without issue, but not something worth doing
right now.
2023-11-22 18:20:15 +00:00
Jonathan Coates
fe826f5c9c Allow generic sources to have instance methods
Rather than assuming static methods are generic, and instance methods
are direct, the Generator now has separate entrypoints for handling
instance and generic methods.

As a result of this change, we've also relaxed some of the validation
code. As a result, we now allow calling private/protected methods
which are annotated with @LuaFunction.
2023-11-22 10:06:11 +00:00
Jonathan Coates
f8b7422294 Fix several issues with the web emulator
- Bump TeaVM version to fix issues with locales and our "export"
   generation.
 - Fix TComputerThread not requeuing correctly.
2023-11-15 13:12:31 +00:00
39 changed files with 732 additions and 505 deletions

View File

@@ -42,7 +42,6 @@ repositories {
url "https://squiddev.cc/maven/"
content {
includeGroup("cc.tweaked")
includeModule("org.squiddev", "Cobalt")
}
}
}

View File

@@ -10,9 +10,11 @@ import org.gradle.api.GradleException
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.TestSuiteType
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.reporting.ReportingExtension
@@ -73,11 +75,17 @@ abstract class CCTweakedExtension(
*/
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
/**
* Dependencies excluded from published artifacts.
*/
private val excludedDeps: ListProperty<Dependency> = project.objects.listProperty(Dependency::class.java)
/** All source sets referenced by this project. */
val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
init {
sourceDirectories.finalizeValueOnRead()
excludedDeps.finalizeValueOnRead()
project.afterEvaluate { sourceDirectories.disallowChanges() }
}
@@ -246,6 +254,20 @@ abstract class CCTweakedExtension(
).resolve().single()
}
/**
* Exclude a dependency from being publisehd in Maven.
*/
fun exclude(dep: Dependency) {
excludedDeps.add(dep)
}
/**
* Configure a [MavenDependencySpec].
*/
fun configureExcludes(spec: MavenDependencySpec) {
for (dep in excludedDeps.get()) spec.exclude(dep)
}
companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf(

View File

@@ -6,6 +6,8 @@ package cc.tweaked.gradle
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.plugins.BasePluginExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.specs.Spec
@@ -26,8 +28,13 @@ class MavenDependencySpec {
fun exclude(dep: Dependency) {
exclude {
// We have to cheat a little for project dependencies, as the project name doesn't match the artifact group.
val name = when (dep) {
is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get()
else -> dep.name
}
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
(dep.name.isNullOrEmpty() || dep.name == it.artifactId) &&
(name.isNullOrEmpty() || name == it.artifactId) &&
(dep.version.isNullOrEmpty() || dep.version == it.version)
}
}

View File

@@ -21,7 +21,7 @@ of the mod should run fine on later versions.
However, some changes to the underlying game, or CC: Tweaked's own internals may break some programs. This page serves
as documentation for breaking changes and "gotchas" one should look out for between versions.
## CC: Tweaked 1.109.0 {#cct-1.109}
## CC: Tweaked 1.109.0 to 1.109.1 {#cct-1.109}
- Update to Lua 5.2:
- Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs.
@@ -31,6 +31,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
- `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
environment.
- Support for dumping functions (`string.dump`) and loading binary chunks has been removed.
- `math.random` now uses Lua 5.4's random number generator.
- File handles, HTTP requests and websockets now always use the original bytes rather than encoding/decoding to UTF-8.

View File

@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=true
modVersion=1.109.0
modVersion=1.109.2
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1

View File

@@ -19,8 +19,8 @@ parchmentMc = "1.20.1"
asm = "9.5"
autoService = "1.1.1"
checkerFramework = "3.32.0"
cobalt = "0.8.0"
cobalt-next = "0.8.1" # Not a real version, used to constrain the version we accept.
cobalt = "0.8.1"
cobalt-next = "0.8.2" # Not a real version, used to constrain the version we accept.
commonsCli = "1.3.1"
fastutil = "8.5.9"
guava = "31.1-jre"
@@ -51,7 +51,7 @@ jqwik = "1.7.4"
junit = "5.10.0"
# Build tools
cctJavadoc = "1.8.1"
cctJavadoc = "1.8.2"
checkstyle = "10.12.3"
curseForgeGradle = "1.0.14"
errorProne-core = "2.21.1"
@@ -68,7 +68,7 @@ mixinGradle = "0.7.+"
nullAway = "0.9.9"
spotless = "6.21.0"
taskTree = "2.1.1"
teavm = "0.10.0-SQUID.1"
teavm = "0.10.0-SQUID.2"
vanillaGradle = "0.2.1-SNAPSHOT"
vineflower = "1.11.0"

View File

@@ -6,7 +6,7 @@
(sources
/doc/
/projects/forge/build/docs/luaJavadoc/
/projects/common/build/docs/luaJavadoc/
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/
/projects/core/src/test/resources/test-rom
@@ -36,7 +36,7 @@
(library-path
/doc/stub/
/projects/forge/build/docs/luaJavadoc/
/projects/common/build/docs/luaJavadoc/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
@@ -88,7 +88,7 @@
(/doc/stub/
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
/projects/forge/build/docs/luaJavadoc/)
/projects/common/build/docs/luaJavadoc/)
(linters -var:unused-global)
(lint (allow-toplevel-global true)))

View File

@@ -2,13 +2,12 @@
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.annotationProcessorEverywhere
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
import cc.tweaked.gradle.*
plugins {
id("cc-tweaked.vanilla")
id("cc-tweaked.gametest")
id("cc-tweaked.illuaminate")
id("cc-tweaked.publishing")
}
@@ -19,6 +18,10 @@ minecraft {
)
}
configurations {
register("cctJavadoc")
}
dependencies {
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
implementation(project(":core"))
@@ -41,4 +44,53 @@ dependencies {
testModImplementation(libs.bundles.kotlin)
testFixturesImplementation(testFixtures(project(":core")))
"cctJavadoc"(libs.cctJavadoc)
}
illuaminate {
version.set(libs.versions.illuaminate)
}
val luaJavadoc by tasks.registering(Javadoc::class) {
description = "Generates documentation for Java-side Lua functions."
group = JavaBasePlugin.DOCUMENTATION_GROUP
val sourceSets = listOf(sourceSets.main.get(), project(":core").sourceSets.main.get())
for (sourceSet in sourceSets) {
source(sourceSet.java)
classpath += sourceSet.compileClasspath
}
destinationDir = layout.buildDirectory.dir("docs/luaJavadoc").get().asFile
val options = options as StandardJavadocDocletOptions
options.docletpath = configurations["cctJavadoc"].files.toList()
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
options.addStringOption("project-root", rootProject.file(".").absolutePath)
options.noTimestamp(false)
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(cc.tweaked.gradle.CCTweakedPlugin.JAVA_VERSION)
},
)
}
val lintLua by tasks.registering(IlluaminateExec::class) {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"
// Config files
inputs.file(rootProject.file("illuaminate.sexp")).withPropertyName("illuaminate.sexp")
// Sources
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
args = listOf("lint")
workingDir = rootProject.projectDir
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}

View File

@@ -19,6 +19,7 @@ import dan200.computercraft.shared.network.server.UploadFileMessage;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
@@ -144,6 +145,11 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|| super.mouseDragged(x, y, button, deltaX, deltaY);
}
@Override
public void setFocused(@Nullable GuiEventListener listener) {
// Don't clear and re-focus if we're already focused.
if (listener != getFocused()) super.setFocused(listener);
}
@Override
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {

View File

@@ -246,7 +246,7 @@ public class TerminalWidget extends AbstractWidget {
keysDown.clear();
// When blurring, we should make the last mouse button go up
if (lastMouseButton > 0) {
if (lastMouseButton >= 0) {
computer.mouseUp(lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1);
lastMouseButton = -1;
}

View File

@@ -58,7 +58,10 @@ class TagProvider {
tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB);
tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE).addTag(BlockTags.CAULDRONS).addTag(BlockTags.BEEHIVES);
tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE)
.addTag(BlockTags.BEEHIVES)
.addTag(BlockTags.CAULDRONS)
.add(Blocks.COMPOSTER);
// Make all blocks aside from command computer mineable.
tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(

View File

@@ -46,12 +46,14 @@ public final class GlobalMetrics {
* Add a new global metrics observer. This will receive metrics data for all computers.
*
* @param tracker The observer to add.
* @return Whether the observer was added. {@code false} if the observer was already registered.
*/
public void addObserver(ComputerMetricsObserver tracker) {
public boolean addObserver(ComputerMetricsObserver tracker) {
synchronized (lock) {
if (trackers.contains(tracker)) return;
if (trackers.contains(tracker)) return false;
trackers.add(tracker);
enabled = true;
return true;
}
}
@@ -59,11 +61,13 @@ public final class GlobalMetrics {
* Remove a previously-registered global metrics observer.
*
* @param tracker The observer to add.
* @return Whether the observer was removed. {@code false} if the observer was not registered.
*/
public void removeObserver(ComputerMetricsObserver tracker) {
public boolean removeObserver(ComputerMetricsObserver tracker) {
synchronized (lock) {
trackers.remove(tracker);
var changed = trackers.remove(tracker);
enabled = !trackers.isEmpty();
return changed;
}
}

View File

@@ -10,6 +10,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.metrics.ComputerMetricsObserver;
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
import javax.annotation.concurrent.GuardedBy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -21,29 +22,31 @@ import java.util.Map;
*/
public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
private final GlobalMetrics owner;
private boolean tracking = false;
@GuardedBy("this")
private final List<ComputerMetrics> timings = new ArrayList<>();
@GuardedBy("this")
private final Map<ServerComputer, ComputerMetrics> timingLookup = new MapMaker().weakKeys().makeMap();
public BasicComputerMetricsObserver(GlobalMetrics owner) {
this.owner = owner;
}
public synchronized void start() {
if (!tracking) owner.addObserver(this);
tracking = true;
public void start() {
if (!owner.addObserver(this)) return;
timings.clear();
timingLookup.clear();
synchronized (this) {
timings.clear();
timingLookup.clear();
}
}
public synchronized boolean stop() {
if (!tracking) return false;
owner.removeObserver(this);
tracking = false;
timingLookup.clear();
public boolean stop() {
if (!owner.removeObserver(this)) return false;
synchronized (this) {
timingLookup.clear();
}
return true;
}
@@ -57,6 +60,7 @@ public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
return new ArrayList<>(timings);
}
@GuardedBy("this")
private ComputerMetrics getMetrics(ServerComputer computer) {
var existing = timingLookup.get(computer);
if (existing != null) return existing;

View File

@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
/**
* Methods for interacting with blocks which store energy.
* <p>
* This works with energy storage blocks, as well as generators and machines which consume energy.
* <p>
* > [!NOTE]
* > Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. FE used/generated per
* > tick).
*
* @param <T> The type for energy storage.
* @cc.module energy_storage
* @cc.since 1.94.0
*/
public abstract class AbstractEnergyMethods<T> implements GenericPeripheral {
@Override
public final PeripheralType getType() {
return PeripheralType.ofAdditional("energy_storage");
}
@Override
public final String id() {
return ComputerCraftAPI.MOD_ID + ":energy";
}
/**
* Get the energy of this block.
*
* @param energy The current energy storage.
* @return The energy stored in this block, in FE.
*/
@LuaFunction(mainThread = true)
public abstract int getEnergy(T energy);
/**
* Get the maximum amount of energy this block can store.
*
* @param energy The current energy storage.
* @return The energy capacity of this block.
*/
@LuaFunction(mainThread = true)
public abstract int getEnergyCapacity(T energy);
}

View File

@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import java.util.Map;
import java.util.Optional;
/**
* Methods for interacting with tanks and other fluid storage blocks.
*
* @param <T> The type for fluid inventories.
* @cc.module fluid_storage
* @cc.since 1.94.0
*/
public abstract class AbstractFluidMethods<T> implements GenericPeripheral {
@Override
public final PeripheralType getType() {
return PeripheralType.ofAdditional("fluid_storage");
}
@Override
public final String id() {
return ComputerCraftAPI.MOD_ID + ":fluid";
}
/**
* Get all "tanks" in this fluid storage.
* <p>
* Each tank either contains some amount of fluid or is empty. Tanks with fluids inside will return some basic
* information about the fluid, including its name and amount.
* <p>
* The returned table is sparse, and so empty tanks will be `nil` - it is recommended to loop over using [`pairs`]
* rather than [`ipairs`].
*
* @param fluids The current fluid handler.
* @return All tanks.
* @cc.treturn { (table|nil)... } All tanks in this fluid storage.
*/
@LuaFunction(mainThread = true)
public abstract Map<Integer, Map<String, ?>> tanks(T fluids);
/**
* Move a fluid from one fluid container to another connected one.
* <p>
* This allows you to pull fluid in the current fluid container to another container <em>on the same wired
* network</em>. Both containers must attached to wired modems which are connected via a cable.
*
* @param from Container to move fluid from.
* @param computer The current computer.
* @param toName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param limit The maximum amount of fluid to move.
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
* @return The amount of moved fluid.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
*/
@LuaFunction(mainThread = true)
public abstract int pushFluid(
T from, IComputerAccess computer, String toName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException;
/**
* Move a fluid from a connected fluid container into this oneone.
* <p>
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
* network</em>. Both containers must attached to wired modems which are connected via a cable.
*
* @param to Container to move fluid to.
* @param computer The current computer.
* @param fromName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param limit The maximum amount of fluid to move.
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
* @return The amount of moved fluid.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
*/
@LuaFunction(mainThread = true)
public abstract int pullFluid(
T to, IComputerAccess computer, String fromName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException;
}

View File

@@ -0,0 +1,197 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Optional;
/**
* Methods for interacting with inventories.
*
* @param <T> The type for inventories.
* @cc.module inventory
* @cc.since 1.94.0
*/
public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
@Override
public final PeripheralType getType() {
return PeripheralType.ofAdditional("inventory");
}
@Override
public final String id() {
return ComputerCraftAPI.MOD_ID + ":inventory";
}
/**
* Get the size of this inventory.
*
* @param inventory The current inventory.
* @return The number of slots in this inventory.
*/
@LuaFunction(mainThread = true)
public abstract int size(T inventory);
/**
* List all items in this inventory. This returns a table, with an entry for each slot.
* <p>
* Each item in the inventory is represented by a table containing some basic information, much like
* {@link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail(ILuaContext, Optional, Optional)}
* includes. More information can be fetched with {@link #getItemDetail}. The table contains the item `name`, the
* `count` and an a (potentially nil) hash of the item's `nbt.` This NBT data doesn't contain anything useful, but
* allows you to distinguish identical items.
* <p>
* The returned table is sparse, and so empty slots will be `nil` - it is recommended to loop over using [`pairs`]
* rather than [`ipairs`].
*
* @param inventory The current inventory.
* @return All items in this inventory.
* @cc.treturn { (table|nil)... } All items in this inventory.
* @cc.usage Find an adjacent chest and print all items in it.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* for slot, item in pairs(chest.list()) do
* print(("%d x %s in slot %d"):format(item.count, item.name, slot))
* end
* }</pre>
*/
@LuaFunction(mainThread = true)
public abstract Map<Integer, Map<String, ?>> list(T inventory);
/**
* Get detailed information about an item.
* <p>
* The returned information contains the same information as each item in
* {@link #list}, as well as additional details like the display name
* (`displayName`), and item and item durability (`damage`, `maxDamage`, `durability`).
* <p>
* Some items include more information (such as enchantments) - it is
* recommended to print it out using [`textutils.serialize`] or in the Lua
* REPL, to explore what is available.
* <p>
* > [Deprecated fields][!INFO]
* > Older versions of CC: Tweaked exposed an {@code itemGroups} field, listing the
* > creative tabs an item was available under. This information is no longer available on
* > more recent versions of the game, and so this field will always be empty. Do not use this
* > field in new code!
*
* @param inventory The current inventory.
* @param slot The slot to get information about.
* @return Information about the item in this slot, or {@code nil} if not present.
* @throws LuaException If the slot is out of range.
* @cc.treturn table Information about the item in this slot, or {@code nil} if not present.
* @cc.usage Print some information about the first in a chest.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local item = chest.getItemDetail(1)
* if not item then print("No item") return end
*
* print(("%s (%s)"):format(item.displayName, item.name))
* print(("Count: %d/%d"):format(item.count, item.maxCount))
*
* if item.damage then
* print(("Damage: %d/%d"):format(item.damage, item.maxDamage))
* end
* }</pre>
*/
@Nullable
@LuaFunction(mainThread = true)
public abstract Map<String, ?> getItemDetail(T inventory, int slot) throws LuaException;
/**
* Get the maximum number of items which can be stored in this slot.
* <p>
* Typically this will be limited to 64 items. However, some inventories (such as barrels or caches) can store
* hundreds or thousands of items in one slot.
*
* @param inventory Inventory to probe.
* @param slot The slot
* @return The maximum number of items in this slot.
* @throws LuaException If the slot is out of range.
* @cc.usage Count the maximum number of items an adjacent chest can hold.
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local total = 0
* for i = 1, chest.size() do
* total = total + chest.getItemLimit(i)
* end
* print(total)
* }</pre>
* @cc.since 1.96.0
*/
@LuaFunction(mainThread = true)
public abstract long getItemLimit(T inventory, int slot) throws LuaException;
/**
* Push items from one inventory to another connected one.
* <p>
* This allows you to push an item in an inventory to another inventory <em>on the same wired network</em>. Both
* inventories must attached to wired modems which are connected via a cable.
*
* @param from Inventory to move items from.
* @param computer The current computer.
* @param toName The name of the peripheral/inventory to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param fromSlot The slot in the current inventory to move items to.
* @param limit The maximum number of items to move. Defaults to the current stack limit.
* @param toSlot The slot in the target inventory to move to. If not given, the item will be inserted into any slot.
* @return The number of transferred items.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
* @throws LuaException If either source or destination slot is out of range.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
* @cc.usage Wrap two chests, and push an item from one to another.
* <pre>{@code
* local chest_a = peripheral.wrap("minecraft:chest_0")
* local chest_b = peripheral.wrap("minecraft:chest_1")
*
* chest_a.pushItems(peripheral.getName(chest_b), 1)
* }</pre>
*/
@LuaFunction(mainThread = true)
public abstract int pushItems(
T from, IComputerAccess computer, String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException;
/**
* Pull items from a connected inventory into this one.
* <p>
* This allows you to transfer items between inventories <em>on the same wired network</em>. Both this and the source
* inventory must attached to wired modems which are connected via a cable.
*
* @param to Inventory to move items to.
* @param computer The current computer.
* @param fromName The name of the peripheral/inventory to pull from. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param fromSlot The slot in the source inventory to move items from.
* @param limit The maximum number of items to move. Defaults to the current stack limit.
* @param toSlot The slot in current inventory to move to. If not given, the item will be inserted into any slot.
* @return The number of transferred items.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
* @throws LuaException If either source or destination slot is out of range.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
* @cc.usage Wrap two chests, and push an item from one to another.
* <pre>{@code
* local chest_a = peripheral.wrap("minecraft:chest_0")
* local chest_b = peripheral.wrap("minecraft:chest_1")
*
* chest_a.pullItems(peripheral.getName(chest_b), 1)
* }</pre>
*/
@LuaFunction(mainThread = true)
public abstract int pullItems(
T to, IComputerAccess computer, String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException;
}

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.api.turtle.TurtleCommandResult;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
import dan200.computercraft.shared.turtle.core.*;
import java.util.Optional;
@@ -749,7 +750,7 @@ public class TurtleAPI implements ILuaAPI {
* -- count = 13,
* -- }
* }</pre>
* @see dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods#getItemDetail Describes the information returned by a detailed query.
* @see AbstractInventoryMethods#getItemDetail Describes the information returned by a detailed query.
*/
@LuaFunction
public final MethodResult getItemDetail(ILuaContext context, Optional<Integer> slot, Optional<Boolean> detailed) throws LuaException {

View File

@@ -11,11 +11,10 @@ import dan200.computercraft.api.peripheral.IPeripheral;
* A generic source of {@link LuaFunction} functions.
* <p>
* Unlike normal objects ({@link IDynamicLuaObject} or {@link IPeripheral}), methods do not target this object but
* instead are defined as {@code static} and accept their target as the first parameter. This allows you to inject
* methods onto objects you do not own, as well as declaring methods for a specific "trait" (for instance, a Forge
* capability or Fabric block lookup interface).
* accept their target as the first parameter. This allows you to inject methods onto objects you do not own, as well as
* declaring methods for a specific "trait" (for instance, a Forge capability or Fabric block lookup interface).
* <p>
* Currently the "generic peripheral" system is incompatible with normal peripherals. Peripherals explicitly provided
* Currently, the "generic peripheral" system is incompatible with normal peripherals. Peripherals explicitly provided
* by capabilities/the block lookup API take priority. Block entities which use this system are given a peripheral name
* determined by their id, rather than any peripheral provider, though additional types may be provided by overriding
* {@link GenericPeripheral#getType()}.
@@ -25,7 +24,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
* <pre>{@code
* public class InventoryMethods implements GenericSource {
* \@LuaFunction( mainThread = true )
* public static int size(IItemHandler inventory) {
* public int size(IItemHandler inventory) {
* return inventory.getSlots();
* }
*

View File

@@ -18,10 +18,7 @@ import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Function;
@@ -106,9 +103,14 @@ final class Generator<T> {
private final Function<MethodHandle, T> factory;
private final Function<T, T> wrap;
private final LoadingCache<Method, Optional<T>> methodCache = CacheBuilder
private final LoadingCache<Method, Optional<T>> instanceCache = CacheBuilder
.newBuilder()
.build(CacheLoader.from(catching(this::build, Optional.empty())));
.build(CacheLoader.from(catching(this::buildInstanceMethod, Optional.empty())));
private final LoadingCache<GenericMethod, Optional<T>> genericCache = CacheBuilder
.newBuilder()
.weakKeys()
.build(CacheLoader.from(catching(this::buildGenericMethod, Optional.empty())));
Generator(List<Class<?>> context, Function<MethodHandle, T> factory, Function<T, T> wrap) {
this.context = context;
@@ -131,65 +133,94 @@ final class Generator<T> {
}
}
Optional<T> getMethod(Method method) {
return methodCache.getUnchecked(method);
Optional<T> getInstanceMethod(Method method) {
return instanceCache.getUnchecked(method);
}
private Optional<T> build(Method method) {
var name = method.getDeclaringClass().getName() + "." + method.getName();
var modifiers = method.getModifiers();
Optional<T> getGenericMethod(GenericMethod method) {
return genericCache.getUnchecked(method);
}
// Instance methods must be final - this prevents them being overridden and potentially exposed twice.
if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
LOG.warn("Lua Method {} should be final.", name);
/**
* Check if a {@link LuaFunction}-annotated method can be used in this context.
*
* @param method The method to check.
* @return Whether the method is valid.
*/
private boolean checkMethod(Method method) {
if (method.isBridge()) {
LOG.debug("Skipping bridge Lua Method {}.{}", method.getDeclaringClass().getName(), method.getName());
return false;
}
if (!Modifier.isPublic(modifiers)) {
LOG.error("Lua Method {} should be a public method.", name);
return Optional.empty();
}
if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
LOG.error("Lua Method {} should be on a public class.", name);
return Optional.empty();
}
LOG.debug("Generating method wrapper for {}.", name);
// Check we don't throw additional exceptions.
var exceptions = method.getExceptionTypes();
for (var exception : exceptions) {
if (exception != LuaException.class) {
LOG.error("Lua Method {} cannot throw {}.", name, exception.getName());
return Optional.empty();
LOG.error("Lua Method {}.{} cannot throw {}.", method.getDeclaringClass().getName(), method.getName(), exception.getName());
return false;
}
}
// unsafe can only be used on the computer thread, so reject it for mainThread functions.
var annotation = method.getAnnotation(LuaFunction.class);
if (annotation.unsafe() && annotation.mainThread()) {
LOG.error("Lua Method {} cannot use unsafe and mainThread", name);
return Optional.empty();
LOG.error("Lua Method {}.{} cannot use unsafe and mainThread.", method.getDeclaringClass().getName(), method.getName());
return false;
}
try {
var originalHandle = LOOKUP.unreflect(method);
List<Type> parameters;
if (Modifier.isStatic(modifiers)) {
var allParameters = method.getGenericParameterTypes();
parameters = Arrays.asList(allParameters).subList(1, allParameters.length);
} else {
parameters = Arrays.asList(method.getGenericParameterTypes());
}
var handle = buildMethodHandle(method, originalHandle, parameters, annotation.unsafe());
if (handle == null) return Optional.empty();
var instance = factory.apply(handle);
return Optional.of(annotation.mainThread() ? wrap.apply(instance) : instance);
} catch (ReflectiveOperationException | RuntimeException e) {
LOG.error("Error generating wrapper for {}.", name, e);
return Optional.empty();
// Instance methods must be final - this prevents them being overridden and potentially exposed twice.
var modifiers = method.getModifiers();
if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers) && !Modifier.isFinal(method.getDeclaringClass().getModifiers())) {
LOG.warn("Lua Method {}.{} should be final.", method.getDeclaringClass().getName(), method.getName());
}
return true;
}
private Optional<T> buildInstanceMethod(Method method) {
if (!checkMethod(method)) return Optional.empty();
var handle = tryUnreflect(method);
if (handle == null) return Optional.empty();
return build(method, handle, Arrays.asList(method.getGenericParameterTypes()));
}
private Optional<T> buildGenericMethod(GenericMethod method) {
if (!checkMethod(method.method)) return Optional.empty();
var handle = tryUnreflect(method.method);
if (handle == null) return Optional.empty();
var parameters = Arrays.asList(method.method.getGenericParameterTypes());
return build(
method.method,
Modifier.isStatic(method.method.getModifiers()) ? handle : handle.bindTo(method.source),
parameters.subList(1, parameters.size()) // Drop the instance argument.
);
}
/**
* Generate our {@link T} instance for a specific method.
* <p>
* This {@linkplain #buildMethodHandle(Member, MethodHandle, List, boolean)} builds the method handle, and then
* wraps it with {@link #factory}.
*
* @param method The original method, for reflection and error reporting.
* @param handle The method handle to execute.
* @param parameters The generic parameters to this method handle.
* @return The generated method, or {@link Optional#empty()} if an error occurred.
*/
private Optional<T> build(Method method, MethodHandle handle, List<Type> parameters) {
LOG.debug("Generating method wrapper for {}.{}.", method.getDeclaringClass().getName(), method.getName());
var annotation = method.getAnnotation(LuaFunction.class);
var wrappedHandle = buildMethodHandle(method, handle, parameters, annotation.unsafe());
if (wrappedHandle == null) return Optional.empty();
var instance = factory.apply(wrappedHandle);
return Optional.of(annotation.mainThread() ? wrap.apply(instance) : instance);
}
/**
@@ -202,8 +233,7 @@ final class Generator<T> {
* @param unsafe Whether to allow unsafe argument getters.
* @return The wrapped method handle.
*/
@Nullable
private MethodHandle buildMethodHandle(Member method, MethodHandle handle, List<Type> parameterTypes, boolean unsafe) {
private @Nullable MethodHandle buildMethodHandle(Member method, MethodHandle handle, List<Type> parameterTypes, boolean unsafe) {
if (handle.type().parameterCount() != parameterTypes.size() + 1) {
throw new IllegalArgumentException("Argument lists are mismatched");
}
@@ -263,8 +293,7 @@ final class Generator<T> {
}
}
@Nullable
private static MethodHandle loadArg(Member method, boolean unsafe, Class<?> argType, Type genericArg, int argIndex) {
private static @Nullable MethodHandle loadArg(Member method, boolean unsafe, Class<?> argType, Type genericArg, int argIndex) {
if (argType == Coerced.class) {
var klass = Reflect.getRawType(method, TypeToken.of(genericArg).resolveType(Reflect.COERCED_IN).getType(), false);
if (klass == null) return null;
@@ -312,6 +341,22 @@ final class Generator<T> {
return null;
}
/**
* A wrapper over {@link MethodHandles.Lookup#unreflect(Method)} which discards errors.
*
* @param method The method to unreflect.
* @return The resulting handle, or {@code null} if it cannot be unreflected.
*/
private static @Nullable MethodHandle tryUnreflect(Method method) {
try {
method.setAccessible(true);
return LOOKUP.unreflect(method);
} catch (SecurityException | InaccessibleObjectException | IllegalAccessException e) {
LOG.error("Lua Method {}.{} is not accessible.", method.getDeclaringClass().getName(), method.getName());
return null;
}
}
@SuppressWarnings("Guava")
static <T, U> com.google.common.base.Function<T, U> catching(Function<T, U> function, U def) {
return x -> {
@@ -320,7 +365,7 @@ final class Generator<T> {
} catch (Exception | LinkageError e) {
// LinkageError due to possible codegen bugs and NoClassDefFoundError. The latter occurs when fetching
// methods on a class which references non-existent (i.e. client-only) types.
LOG.error("Error generating @LuaFunctions", e);
LOG.error("Error generating @LuaFunction for {}", x, e);
return def;
}
};

View File

@@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;
@@ -56,16 +55,11 @@ public final class GenericMethod {
Class<?> klass = source.getClass();
var type = source instanceof GenericPeripheral generic ? generic.getType() : null;
return Arrays.stream(klass.getDeclaredMethods())
return Arrays.stream(klass.getMethods())
.map(method -> {
var annotation = method.getAnnotation(LuaFunction.class);
if (annotation == null) return null;
if (!Modifier.isStatic(method.getModifiers())) {
LOG.error("GenericSource method {}.{} should be static.", method.getDeclaringClass(), method.getName());
return null;
}
var types = method.getGenericParameterTypes();
if (types.length == 0) {
LOG.error("GenericSource method {}.{} has no parameters.", method.getDeclaringClass(), method.getName());

View File

@@ -110,7 +110,7 @@ final class MethodSupplierImpl<T> implements MethodSupplier<T> {
continue;
}
var instance = generator.getMethod(method).orElse(null);
var instance = generator.getInstanceMethod(method).orElse(null);
if (instance == null) continue;
if (methods == null) methods = new ArrayList<>();
@@ -121,7 +121,7 @@ final class MethodSupplierImpl<T> implements MethodSupplier<T> {
for (var method : genericMethods) {
if (!method.target.isAssignableFrom(klass)) continue;
var instance = generator.getMethod(method.method).orElse(null);
var instance = generator.getGenericMethod(method).orElse(null);
if (instance == null) continue;
if (methods == null) methods = new ArrayList<>();

View File

@@ -35,6 +35,7 @@ local term = _ENV
-- @since 1.31
-- @usage
-- Redirect to a monitor on the right of the computer.
--
-- term.redirect(peripheral.wrap("right"))
term.redirect = function(target)
expect(1, target, "table")

View File

@@ -1,3 +1,18 @@
# New features in CC: Tweaked 1.109.2
* `math.random` now uses Lua 5.4's random number generator.
Several bug fixes:
* Fix errors involving `goto` statements having the wrong line number.
# New features in CC: Tweaked 1.109.1
Several bug fixes:
* Fix `mouse_drag` event not firing for right and middle mouse buttons.
* Fix crash when syntax errors involve `goto` or `::`.
* Fix deadlock occuring when adding/removing observers.
* Allow placing seeds into compostor barrels with `turtle.place()`.
# New features in CC: Tweaked 1.109.0
* Update to Lua 5.2

View File

@@ -1,15 +1,8 @@
New features in CC: Tweaked 1.109.0
New features in CC: Tweaked 1.109.2
* Update to Lua 5.2
* `getfenv`/`setfenv` now only work on Lua functions.
* Add support for `goto`.
* Remove support for dumping and loading binary chunks.
* File handles, HTTP requests and websocket messages now use raw bytes rather than converting to UTF-8.
* Add `allow_repetitions` option to `textutils.serialiseJSON`.
* Track memory allocated by computers.
* `math.random` now uses Lua 5.4's random number generator.
Several bug fixes:
* Fix error when using position captures and backreferences in string patterns (e.g. `()(%1)`).
* Fix formatting non-real numbers with `%d`.
* Fix errors involving `goto` statements having the wrong line number.
Type "help changelog" to see the full version history.

View File

@@ -58,6 +58,7 @@ local token_names = setmetatable({
[tokens.DO] = code("do"),
[tokens.DOT] = code("."),
[tokens.DOTS] = code("..."),
[tokens.DOUBLE_COLON] = code("::"),
[tokens.ELSE] = code("else"),
[tokens.ELSEIF] = code("elseif"),
[tokens.END] = code("end"),
@@ -67,6 +68,7 @@ local token_names = setmetatable({
[tokens.FOR] = code("for"),
[tokens.FUNCTION] = code("function"),
[tokens.GE] = code(">="),
[tokens.GOTO] = code("goto"),
[tokens.GT] = code(">"),
[tokens.IF] = code("if"),
[tokens.IN] = code("in"),

View File

@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import static dan200.computercraft.test.core.ContramapMatcher.contramap;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -30,7 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class GeneratorTest {
private static final MethodSupplierImpl<LuaMethod> GENERATOR = (MethodSupplierImpl<LuaMethod>) LuaMethodSupplier.create(
GenericMethod.getMethods(new StaticMethod()).toList()
Stream.of(new StaticGeneric(), new InstanceGeneric()).flatMap(GenericMethod::getMethods).toList()
);
@Test
@@ -65,8 +66,10 @@ public class GeneratorTest {
}
@Test
public void testNonPublicClass() {
assertThat(GENERATOR.getMethods(NonPublic.class), is(empty()));
public void testNonPublicClass() throws LuaException {
var methods = GENERATOR.getMethods(NonPublic.class);
assertThat(methods, contains(named("go")));
assertThat(apply(methods, new NonPublic(), "go"), is(MethodResult.of()));
}
@Test
@@ -75,10 +78,18 @@ public class GeneratorTest {
}
@Test
public void testStaticMethod() throws LuaException {
var methods = GENERATOR.getMethods(StaticMethodTarget.class);
assertThat(methods, contains(named("go")));
assertThat(apply(methods, new StaticMethodTarget(), "go", "Hello", 123), is(MethodResult.of()));
public void testStaticGenericMethod() throws LuaException {
var methods = GENERATOR.getMethods(GenericMethodTarget.class);
assertThat(methods, hasItem(named("goStatic")));
assertThat(apply(methods, new GenericMethodTarget(), "goStatic", "Hello", 123), is(MethodResult.of()));
}
@Test
public void testInstanceGenericrMethod() throws LuaException {
var methods = GENERATOR.getMethods(GenericMethodTarget.class);
assertThat(methods, hasItem(named("goInstance")));
assertThat(apply(methods, new GenericMethodTarget(), "goInstance", "Hello", 123), is(MethodResult.of()));
}
@Test
@@ -181,17 +192,28 @@ public class GeneratorTest {
}
}
public static class StaticMethodTarget {
public static class GenericMethodTarget {
}
public static class StaticMethod implements GenericSource {
public static class StaticGeneric implements GenericSource {
@Override
public String id() {
return "source";
return "static";
}
@LuaFunction
public static void go(StaticMethodTarget target, String arg1, int arg2, ILuaContext context) {
public static void goStatic(GenericMethodTarget target, String arg1, int arg2, ILuaContext context) {
}
}
public static class InstanceGeneric implements GenericSource {
@Override
public String id() {
return "instance";
}
@LuaFunction
public void goInstance(GenericMethodTarget target, String arg1, int arg2, ILuaContext context) {
}
}

View File

@@ -48,7 +48,7 @@ addRemappedConfiguration("testWithIris")
dependencies {
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
modImplementation(libs.bundles.externalMods.fabric)
modImplementation(libs.bundles.externalMods.fabric) { cct.exclude(this) }
modCompileOnly(libs.bundles.externalMods.fabric.compile) {
exclude("net.fabricmc", "fabric-loader")
exclude("net.fabricmc.fabric-api")
@@ -72,9 +72,9 @@ dependencies {
include(libs.nightConfig.toml)
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
api(commonClasses(project(":fabric-api")))
clientApi(clientClasses(project(":fabric-api")))
implementation(project(":core"))
api(commonClasses(project(":fabric-api"))) { cct.exclude(this) }
clientApi(clientClasses(project(":fabric-api"))) { cct.exclude(this) }
implementation(project(":core")) { cct.exclude(this) }
// These are transitive deps of :core, so we don't need these deps. However, we want them to appear as runtime deps
// in our POM, and this is the easiest way.
runtimeOnly(libs.cobalt)
@@ -168,7 +168,11 @@ loom {
configureForGameTest(this)
property("fabric-api.gametest")
property("fabric-api.gametest.report-file", layout.buildDirectory.dir("test-results/runGametest.xml").getAbsolutePath())
property(
"fabric-api.gametest.report-file",
layout.buildDirectory.dir("test-results/runGametest.xml")
.getAbsolutePath(),
)
runDir("run/gametest")
}
}
@@ -258,9 +262,7 @@ publishing {
publications {
named("maven", MavenPublication::class) {
mavenDependencies {
exclude(dependencies.create("cc.tweaked:"))
exclude(libs.jei.fabric.get())
exclude(libs.modmenu.get())
cct.configureExcludes(this)
}
}
}

View File

@@ -1 +1 @@
{"replace": false, "values": ["#minecraft:cauldrons", "#minecraft:beehives"]}
{"replace": false, "values": ["#minecraft:beehives", "#minecraft:cauldrons", "minecraft:composter"]}

View File

@@ -4,14 +4,11 @@
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.platform.FabricContainerTransfer;
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
@@ -32,37 +29,30 @@ import java.util.Optional;
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
/**
* Methods for interacting with inventories. This mirrors the Forge version.
* Inventory methods for Fabric's {@link SlottedStorage} and {@link ItemVariant}s.
* <p>
* The generic peripheral system doesn't (currently) support generics, and so we need to wrap this in a
* {@link StorageWrapper} box.
*/
@SuppressWarnings("UnstableApiUsage")
public class InventoryMethods implements GenericPeripheral {
@Override
public PeripheralType getType() {
return PeripheralType.ofAdditional("inventory");
}
@Override
public String id() {
return ComputerCraftAPI.MOD_ID + ":inventory";
}
public final class InventoryMethods extends AbstractInventoryMethods<InventoryMethods.StorageWrapper> {
/**
* Wrapper over a {@link SlottedStorage}.
* <p>
* The generic peripheral system doesn't (currently) support generics, and so we need put the inventory in a box.
*
* @param storage The underlying storage
*/
public record StorageWrapper(SlottedStorage<ItemVariant> storage) {
}
@Override
@LuaFunction(mainThread = true)
public static int size(StorageWrapper inventory) {
public int size(StorageWrapper inventory) {
return inventory.storage().getSlots().size();
}
@Override
@LuaFunction(mainThread = true)
public static Map<Integer, Map<String, ?>> list(StorageWrapper inventory) {
public Map<Integer, Map<String, ?>> list(StorageWrapper inventory) {
Map<Integer, Map<String, ?>> result = new HashMap<>();
var slots = inventory.storage().getSlots();
var size = slots.size();
@@ -74,23 +64,26 @@ public class InventoryMethods implements GenericPeripheral {
return result;
}
@Override
@Nullable
@LuaFunction(mainThread = true)
public static Map<String, ?> getItemDetail(StorageWrapper inventory, int slot) throws LuaException {
public Map<String, ?> getItemDetail(StorageWrapper inventory, int slot) throws LuaException {
assertBetween(slot, 1, inventory.storage().getSlotCount(), "Slot out of range (%s)");
var stack = toStack(inventory.storage().getSlot(slot - 1));
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
}
@Override
@LuaFunction(mainThread = true)
public static long getItemLimit(StorageWrapper inventory, int slot) throws LuaException {
public long getItemLimit(StorageWrapper inventory, int slot) throws LuaException {
assertBetween(slot, 1, inventory.storage().getSlotCount(), "Slot out of range (%s)");
return inventory.storage().getSlot(slot - 1).getCapacity();
}
@Override
@LuaFunction(mainThread = true)
public static int pushItems(
public int pushItems(
StorageWrapper from, IComputerAccess computer,
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException {
@@ -112,8 +105,9 @@ public class InventoryMethods implements GenericPeripheral {
return moveItem(fromStorage, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
}
@Override
@LuaFunction(mainThread = true)
public static int pullItems(
public int pullItems(
StorageWrapper to, IComputerAccess computer,
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException {

View File

@@ -9,7 +9,6 @@ plugins {
id("cc-tweaked.forge")
id("cc-tweaked.gametest")
alias(libs.plugins.mixinGradle)
id("cc-tweaked.illuaminate")
id("cc-tweaked.mod-publishing")
}
@@ -117,7 +116,6 @@ mixin {
}
configurations {
register("cctJavadoc")
minecraftLibrary { extendsFrom(minecraftEmbed.get()) }
}
@@ -133,9 +131,9 @@ dependencies {
libs.bundles.externalMods.forge.runtime.get().map { runtimeOnly(fg.deobf(it)) }
// Depend on our other projects.
api(commonClasses(project(":forge-api")))
api(clientClasses(project(":forge-api")))
implementation(project(":core"))
api(commonClasses(project(":forge-api"))) { cct.exclude(this) }
clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) }
implementation(project(":core")) { cct.exclude(this) }
minecraftEmbed(libs.cobalt) {
jarJar.ranged(this, "[${libs.versions.cobalt.asProvider().get()},${libs.versions.cobalt.next.get()})")
@@ -167,40 +165,10 @@ dependencies {
testModImplementation(testFixtures(project(":forge")))
testFixturesImplementation(testFixtures(project(":core")))
"cctJavadoc"(libs.cctJavadoc)
}
illuaminate {
version.set(libs.versions.illuaminate)
}
// Compile tasks
val luaJavadoc by tasks.registering(Javadoc::class) {
description = "Generates documentation for Java-side Lua functions."
group = JavaBasePlugin.DOCUMENTATION_GROUP
source(sourceSets.main.get().java)
source(project(":core").sourceSets.main.get().java)
source(project(":common").sourceSets.main.get().java)
destinationDir = layout.buildDirectory.dir("docs/luaJavadoc").get().asFile
classpath = sourceSets.main.get().compileClasspath
val options = options as StandardJavadocDocletOptions
options.docletpath = configurations["cctJavadoc"].files.toList()
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
options.addStringOption("project-root", rootProject.file(".").absolutePath)
options.noTimestamp(false)
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(cc.tweaked.gradle.CCTweakedPlugin.JAVA_VERSION)
},
)
}
tasks.processResources {
inputs.property("modVersion", modVersion)
inputs.property("forgeVersion", libs.versions.forge.get())
@@ -240,24 +208,6 @@ tasks.test {
systemProperty("cct.test-files", layout.buildDirectory.dir("tmp/testFiles").getAbsolutePath())
}
val lintLua by tasks.registering(IlluaminateExec::class) {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"
// Config files
inputs.file(rootProject.file("illuaminate.sexp")).withPropertyName("illuaminate.sexp")
// Sources
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
args = listOf("lint")
workingDir = rootProject.projectDir
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}
val runGametest by tasks.registering(JavaExec::class) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Runs tests on a temporary Minecraft instance."
@@ -304,7 +254,7 @@ publishing {
artifact(tasks.jarJar)
mavenDependencies {
exclude(dependencies.create("cc.tweaked:"))
cct.configureExcludes(this)
exclude(libs.jei.forge.get())
}
}

View File

@@ -1 +1 @@
{"values": ["#minecraft:cauldrons", "#minecraft:beehives"]}
{"values": ["#minecraft:beehives", "#minecraft:cauldrons", "minecraft:composter"]}

View File

@@ -4,54 +4,22 @@
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import net.minecraftforge.energy.IEnergyStorage;
/**
* Methods for interacting with blocks using Forge's energy storage system.
* <p>
* This works with energy storage blocks, as well as generators and machines which consume energy.
* <p>
* > [!NOTE]
* > Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF
* > used/generated per tick).
*
* @cc.module energy_storage
* @cc.since 1.94.0
* Fluid methods for Forge's {@link IEnergyStorage}.
*/
public class EnergyMethods implements GenericPeripheral {
public final class EnergyMethods extends AbstractEnergyMethods<IEnergyStorage> {
@Override
public PeripheralType getType() {
return PeripheralType.ofAdditional("energy_storage");
}
@Override
public String id() {
return ComputerCraftAPI.MOD_ID + ":energy";
}
/**
* Get the energy of this block.
*
* @param energy The current energy storage.
* @return The energy stored in this block, in FE.
*/
@LuaFunction(mainThread = true)
public static int getEnergy(IEnergyStorage energy) {
public int getEnergy(IEnergyStorage energy) {
return energy.getEnergyStored();
}
/**
* Get the maximum amount of energy this block can store.
*
* @param energy The current energy storage.
* @return The energy capacity of this block.
*/
@Override
@LuaFunction(mainThread = true)
public static int getEnergyCapacity(IEnergyStorage energy) {
public int getEnergyCapacity(IEnergyStorage energy) {
return energy.getMaxEnergyStored();
}
}

View File

@@ -4,13 +4,10 @@
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.ForgeDetailRegistries;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
@@ -26,37 +23,12 @@ import java.util.Optional;
import static dan200.computercraft.shared.util.ArgumentHelpers.getRegistryEntry;
/**
* Methods for interacting with tanks and other fluid storage blocks.
*
* @cc.module fluid_storage
* @cc.since 1.94.0
* Fluid methods for Forge's {@link IFluidHandler}.
*/
public class FluidMethods implements GenericPeripheral {
public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
@Override
public PeripheralType getType() {
return PeripheralType.ofAdditional("fluid_storage");
}
@Override
public String id() {
return ComputerCraftAPI.MOD_ID + ":fluid";
}
/**
* Get all "tanks" in this fluid storage.
* <p>
* Each tank either contains some amount of fluid or is empty. Tanks with fluids inside will return some basic
* information about the fluid, including its name and amount.
* <p>
* The returned table is sparse, and so empty tanks will be `nil` - it is recommended to loop over using `pairs`
* rather than `ipairs`.
*
* @param fluids The current fluid handler.
* @return All tanks.
* @cc.treturn { (table|nil)... } All tanks in this fluid storage.
*/
@LuaFunction(mainThread = true)
public static Map<Integer, Map<String, ?>> tanks(IFluidHandler fluids) {
public Map<Integer, Map<String, ?>> tanks(IFluidHandler fluids) {
Map<Integer, Map<String, ?>> result = new HashMap<>();
var size = fluids.getTanks();
for (var i = 0; i < size; i++) {
@@ -67,24 +39,9 @@ public class FluidMethods implements GenericPeripheral {
return result;
}
/**
* Move a fluid from one fluid container to another connected one.
* <p>
* This allows you to pull fluid in the current fluid container to another container <em>on the same wired
* network</em>. Both containers must attached to wired modems which are connected via a cable.
*
* @param from Container to move fluid from.
* @param computer The current computer.
* @param toName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param limit The maximum amount of fluid to move.
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
* @return The amount of moved fluid.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
*/
@Override
@LuaFunction(mainThread = true)
public static int pushFluid(
public int pushFluid(
IFluidHandler from, IComputerAccess computer,
String toName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException {
@@ -107,24 +64,9 @@ public class FluidMethods implements GenericPeripheral {
: moveFluid(from, new FluidStack(fluid, actualLimit), to);
}
/**
* Move a fluid from a connected fluid container into this oneone.
* <p>
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
* network</em>. Both containers must attached to wired modems which are connected via a cable.
*
* @param to Container to move fluid to.
* @param computer The current computer.
* @param fromName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param limit The maximum amount of fluid to move.
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
* @return The amount of moved fluid.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
*/
@Override
@LuaFunction(mainThread = true)
public static int pullFluid(
public int pullFluid(
IFluidHandler to, IComputerAccess computer,
String fromName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException {
@@ -194,7 +136,7 @@ public class FluidMethods implements GenericPeripheral {
* @return The amount of fluid moved.
*/
private static int moveFluid(IFluidHandler from, FluidStack extracted, int limit, IFluidHandler to) {
if (extracted == null || extracted.getAmount() <= 0) return 0;
if (extracted.getAmount() <= 0) return 0;
// Limit the amount to extract.
extracted = extracted.copy();

View File

@@ -4,14 +4,10 @@
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.platform.ForgeContainerTransfer;
import net.minecraft.world.Container;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -28,59 +24,18 @@ import java.util.Optional;
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
/**
* Methods for interacting with inventories.
*
* @cc.module inventory
* @cc.since 1.94.0
* Inventory methods for Forge's {@link IItemHandler}.
*/
public class InventoryMethods implements GenericPeripheral {
public final class InventoryMethods extends AbstractInventoryMethods<IItemHandler> {
@Override
public PeripheralType getType() {
return PeripheralType.ofAdditional("inventory");
}
@Override
public String id() {
return ComputerCraftAPI.MOD_ID + ":inventory";
}
/**
* Get the size of this inventory.
*
* @param inventory The current inventory.
* @return The number of slots in this inventory.
*/
@LuaFunction(mainThread = true)
public static int size(IItemHandler inventory) {
public int size(IItemHandler inventory) {
return inventory.getSlots();
}
/**
* List all items in this inventory. This returns a table, with an entry for each slot.
* <p>
* Each item in the inventory is represented by a table containing some basic information, much like
* {@link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail(ILuaContext, Optional, Optional)}
* includes. More information can be fetched with {@link #getItemDetail}. The table contains the item `name`, the
* `count` and an a (potentially nil) hash of the item's `nbt.` This NBT data doesn't contain anything useful, but
* allows you to distinguish identical items.
* <p>
* The returned table is sparse, and so empty slots will be `nil` - it is recommended to loop over using `pairs`
* rather than `ipairs`.
*
* @param inventory The current inventory.
* @return All items in this inventory.
* @cc.treturn { (table|nil)... } All items in this inventory.
* @cc.usage Find an adjacent chest and print all items in it.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* for slot, item in pairs(chest.list()) do
* print(("%d x %s in slot %d"):format(item.count, item.name, slot))
* end
* }</pre>
*/
@Override
@LuaFunction(mainThread = true)
public static Map<Integer, Map<String, ?>> list(IItemHandler inventory) {
public Map<Integer, Map<String, ?>> list(IItemHandler inventory) {
Map<Integer, Map<String, ?>> result = new HashMap<>();
var size = inventory.getSlots();
for (var i = 0; i < size; i++) {
@@ -91,106 +46,26 @@ public class InventoryMethods implements GenericPeripheral {
return result;
}
/**
* Get detailed information about an item.
* <p>
* The returned information contains the same information as each item in
* {@link #list}, as well as additional details like the display name
* (`displayName`), and item and item durability (`damage`, `maxDamage`, `durability`).
* <p>
* Some items include more information (such as enchantments) - it is
* recommended to print it out using [`textutils.serialize`] or in the Lua
* REPL, to explore what is available.
* <p>
* > [Deprecated fields][!INFO]
* > Older versions of CC: Tweaked exposed an {@code itemGroups} field, listing the
* > creative tabs an item was available under. This information is no longer available on
* > more recent versions of the game, and so this field will always be empty. Do not use this
* > field in new code!
*
* @param inventory The current inventory.
* @param slot The slot to get information about.
* @return Information about the item in this slot, or {@code nil} if not present.
* @throws LuaException If the slot is out of range.
* @cc.treturn table Information about the item in this slot, or {@code nil} if not present.
* @cc.usage Print some information about the first in a chest.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local item = chest.getItemDetail(1)
* if not item then print("No item") return end
*
* print(("%s (%s)"):format(item.displayName, item.name))
* print(("Count: %d/%d"):format(item.count, item.maxCount))
*
* if item.damage then
* print(("Damage: %d/%d"):format(item.damage, item.maxDamage))
* end
* }</pre>
*/
@Override
@Nullable
@LuaFunction(mainThread = true)
public static Map<String, ?> getItemDetail(IItemHandler inventory, int slot) throws LuaException {
public Map<String, ?> getItemDetail(IItemHandler inventory, int slot) throws LuaException {
assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
var stack = inventory.getStackInSlot(slot - 1);
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
}
/**
* Get the maximum number of items which can be stored in this slot.
* <p>
* Typically this will be limited to 64 items. However, some inventories (such as barrels or caches) can store
* hundreds or thousands of items in one slot.
*
* @param inventory Inventory to probe.
* @param slot The slot
* @return The maximum number of items in this slot.
* @throws LuaException If the slot is out of range.
* @cc.usage Count the maximum number of items an adjacent chest can hold.
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local total = 0
* for i = 1, chest.size() do
* total = total + chest.getItemLimit(i)
* end
* print(total)
* }</pre>
* @cc.since 1.96.0
*/
@Override
@LuaFunction(mainThread = true)
public static int getItemLimit(IItemHandler inventory, int slot) throws LuaException {
public long getItemLimit(IItemHandler inventory, int slot) throws LuaException {
assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
return inventory.getSlotLimit(slot - 1);
}
/**
* Push items from one inventory to another connected one.
* <p>
* This allows you to push an item in an inventory to another inventory <em>on the same wired network</em>. Both
* inventories must attached to wired modems which are connected via a cable.
*
* @param from Inventory to move items from.
* @param computer The current computer.
* @param toName The name of the peripheral/inventory to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param fromSlot The slot in the current inventory to move items to.
* @param limit The maximum number of items to move. Defaults to the current stack limit.
* @param toSlot The slot in the target inventory to move to. If not given, the item will be inserted into any slot.
* @return The number of transferred items.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
* @throws LuaException If either source or destination slot is out of range.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
* @cc.usage Wrap two chests, and push an item from one to another.
* <pre>{@code
* local chest_a = peripheral.wrap("minecraft:chest_0")
* local chest_b = peripheral.wrap("minecraft:chest_1")
*
* chest_a.pushItems(peripheral.getName(chest_b), 1)
* }</pre>
*/
@Override
@LuaFunction(mainThread = true)
public static int pushItems(
public int pushItems(
IItemHandler from, IComputerAccess computer,
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException {
@@ -210,33 +85,9 @@ public class InventoryMethods implements GenericPeripheral {
return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
}
/**
* Pull items from a connected inventory into this one.
* <p>
* This allows you to transfer items between inventories <em>on the same wired network</em>. Both this and the source
* inventory must attached to wired modems which are connected via a cable.
*
* @param to Inventory to move items to.
* @param computer The current computer.
* @param fromName The name of the peripheral/inventory to pull from. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param fromSlot The slot in the source inventory to move items from.
* @param limit The maximum number of items to move. Defaults to the current stack limit.
* @param toSlot The slot in current inventory to move to. If not given, the item will be inserted into any slot.
* @return The number of transferred items.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
* @throws LuaException If either source or destination slot is out of range.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
* @cc.usage Wrap two chests, and push an item from one to another.
* <pre>{@code
* local chest_a = peripheral.wrap("minecraft:chest_0")
* local chest_b = peripheral.wrap("minecraft:chest_1")
*
* chest_a.pullItems(peripheral.getName(chest_b), 1)
* }</pre>
*/
@Override
@LuaFunction(mainThread = true)
public static int pullItems(
public int pullItems(
IItemHandler to, IComputerAccess computer,
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException {

View File

@@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Objects;
@@ -97,7 +98,7 @@ public class Main {
} catch (ParseException e) {
System.err.println(e.getLocalizedMessage());
var writer = new PrintWriter(System.err);
var writer = new PrintWriter(System.err, false, StandardCharsets.UTF_8);
new HelpFormatter().printUsage(writer, HelpFormatter.DEFAULT_WIDTH, "standalone.jar", options);
writer.flush();

View File

@@ -30,12 +30,12 @@ The code for this is split into three separate components:
## Static content
Rendering the static portion of the website is fortunately much simpler.
- Doc generation: This is mostly handled in various Gradle files. The Forge Gradle script uses [cct-javadoc] to convert
- Doc generation: This is mostly handled in various Gradle files. The `common` project uses [cct-javadoc] to convert
Javadoc on our peripherals and APIs to LDoc/[illuaminate] compatible documentation. This is then fed into illuaminate
which spits out HTML.
- `src/htmlTransform`: We do a small amount of post-processing on the HTML in order. This project does syntax
highlighting of non-Lua code blocks, and replaces special `<mc-recipe>` tags with a rendered view of a given
- `src/htmlTransform`: We do a small amount of post-processing on the HTML, which is performed by this tool. This includes
syntax highlighting of non-Lua code blocks, and replacing special `<mc-recipe>` tags with a rendered view of a given
Minecraft recipe.
[TeaVM]: https://github.com/konsoletyper/teavm "TeaVM - Compiler of Java bytecode to JavaScript"

View File

@@ -94,7 +94,7 @@ val illuaminateDocs by tasks.registering(cc.tweaked.gradle.IlluaminateExecToDir:
// Sources
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(project(":forge").tasks.named("luaJavadoc"))
inputs.files(project(":common").tasks.named("luaJavadoc"))
// Assets
inputs.files(rollup)

View File

@@ -87,21 +87,16 @@ public final class StaticGenerator<T> {
var name = method.getDeclaringClass().getName() + "." + method.getName();
var modifiers = method.getModifiers();
// Instance methods must be final - this prevents them being overridden and potentially exposed twice.
if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
// Methods must be final - this prevents them being overridden and potentially exposed twice.
if (!Modifier.isFinal(modifiers) && !Modifier.isFinal(method.getDeclaringClass().getModifiers())) {
System.err.printf("Lua Method %s should be final.\n", name);
}
if (!Modifier.isPublic(modifiers)) {
if (!Modifier.isPublic(modifiers) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
System.err.printf("Lua Method %s should be a public method.\n", name);
return Optional.empty();
}
if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
System.err.printf("Lua Method %s should be on a public class.\n", name);
return Optional.empty();
}
var exceptions = method.getExceptionTypes();
for (var exception : exceptions) {
if (exception != LuaException.class) {
@@ -116,11 +111,8 @@ public final class StaticGenerator<T> {
return Optional.empty();
}
// We have some rather ugly handling of static methods in both here and the main generate function. Static methods
// only come from generic sources, so this should be safe.
var target = Modifier.isStatic(modifiers) ? method.getParameterTypes()[0] : method.getDeclaringClass();
try {
var target = method.getDeclaringClass();
var bytes = generate(classPrefix + method.getDeclaringClass().getSimpleName() + "$" + method.getName(), target, method, annotation.unsafe());
if (bytes == null) return Optional.empty();
@@ -170,11 +162,9 @@ public final class StaticGenerator<T> {
var mw = cw.visitMethod(ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS);
mw.visitCode();
// If we're an instance method, load the target as the first argument.
if (!Modifier.isStatic(targetMethod.getModifiers())) {
mw.visitVarInsn(ALOAD, 1);
mw.visitTypeInsn(CHECKCAST, Type.getInternalName(target));
}
// Load the target as the first argument.
mw.visitVarInsn(ALOAD, 1);
mw.visitTypeInsn(CHECKCAST, Type.getInternalName(target));
var argIndex = 0;
for (var genericArg : targetMethod.getGenericParameterTypes()) {
@@ -184,7 +174,7 @@ public final class StaticGenerator<T> {
}
mw.visitMethodInsn(
Modifier.isStatic(targetMethod.getModifiers()) ? INVOKESTATIC : INVOKEVIRTUAL,
INVOKEVIRTUAL,
Type.getInternalName(targetMethod.getDeclaringClass()), targetMethod.getName(),
Type.getMethodDescriptor(targetMethod), false
);
@@ -244,12 +234,10 @@ public final class StaticGenerator<T> {
if (klass == null) return null;
if (klass == String.class) {
mw.visitTypeInsn(NEW, INTERNAL_COERCED);
mw.visitInsn(DUP);
mw.visitVarInsn(ALOAD, 2 + context.size());
loadInt(mw, argIndex);
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "getStringCoerced", "(I)Ljava/lang/String;", true);
mw.visitMethodInsn(INVOKESPECIAL, INTERNAL_COERCED, "<init>", "(Ljava/lang/Object;)V", false);
loadCoerced(mw, argIndex, "getStringCoerced", "(I)Ljava/lang/String;");
return true;
} else if (klass == ByteBuffer.class) {
loadCoerced(mw, argIndex, "getBytesCoerced", "(I)Ljava/nio/ByteBuffer;");
return true;
}
}
@@ -324,6 +312,15 @@ public final class StaticGenerator<T> {
}
}
private void loadCoerced(MethodVisitor mw, int argIndex, String getter, String getterType) {
mw.visitTypeInsn(NEW, INTERNAL_COERCED);
mw.visitInsn(DUP);
mw.visitVarInsn(ALOAD, 2 + context.size());
loadInt(mw, argIndex);
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, getter, getterType, true);
mw.visitMethodInsn(INVOKESPECIAL, INTERNAL_COERCED, "<init>", "(Ljava/lang/Object;)V", false);
}
@SuppressWarnings("Guava")
static <T, U> com.google.common.base.Function<T, U> catching(Function<T, U> function, U def) {
return x -> {

View File

@@ -28,6 +28,7 @@ public class TComputerThread implements ComputerScheduler {
private static final ArrayDeque<ExecutorImpl> executors = new ArrayDeque<>();
private static final TimerHandler callback = TComputerThread::workOnce;
private static boolean enqueued;
public TComputerThread(int threads) {
}
@@ -38,6 +39,8 @@ public class TComputerThread implements ComputerScheduler {
}
private static void workOnce() {
enqueued = false;
var executor = executors.poll();
if (executor == null) throw new IllegalStateException("Working, but executor is null");
@@ -50,7 +53,14 @@ public class TComputerThread implements ComputerScheduler {
}
executor.afterWork();
if (!executors.isEmpty()) Callbacks.setImmediate(callback);
if (!executors.isEmpty()) enqueue();
}
private static void enqueue() {
if (enqueued) return;
enqueued = true;
Callbacks.setImmediate(callback);
}
@Override
@@ -75,8 +85,8 @@ public class TComputerThread implements ComputerScheduler {
if (onQueue) return;
onQueue = true;
if (executors.isEmpty()) Callbacks.setImmediate(callback);
executors.add(this);
enqueue();
}
void beforeWork() {