1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-07 17:03:00 +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/" url "https://squiddev.cc/maven/"
content { content {
includeGroup("cc.tweaked") 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.NamedDomainObjectProvider
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.TestSuiteType import org.gradle.api.attributes.TestSuiteType
import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.FileSystemOperations
import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty import org.gradle.api.provider.SetProperty
import org.gradle.api.reporting.ReportingExtension import org.gradle.api.reporting.ReportingExtension
@@ -73,11 +75,17 @@ abstract class CCTweakedExtension(
*/ */
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java) 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. */ /** All source sets referenced by this project. */
val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } } val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
init { init {
sourceDirectories.finalizeValueOnRead() sourceDirectories.finalizeValueOnRead()
excludedDeps.finalizeValueOnRead()
project.afterEvaluate { sourceDirectories.disallowChanges() } project.afterEvaluate { sourceDirectories.disallowChanges() }
} }
@@ -246,6 +254,20 @@ abstract class CCTweakedExtension(
).resolve().single() ).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 { companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""") private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf( 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.Dependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency 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.publish.maven.MavenPublication
import org.gradle.api.specs.Spec import org.gradle.api.specs.Spec
@@ -26,8 +28,13 @@ class MavenDependencySpec {
fun exclude(dep: Dependency) { fun exclude(dep: Dependency) {
exclude { 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.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) (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 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. 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: - Update to Lua 5.2:
- Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs. - 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 - `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
environment. environment.
- Support for dumping functions (`string.dump`) and loading binary chunks has been removed. - 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. - 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 # Mod properties
isUnstable=true 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 # Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1 mcVersion=1.20.1

View File

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

View File

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

View File

@@ -2,13 +2,12 @@
// //
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.annotationProcessorEverywhere import cc.tweaked.gradle.*
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
plugins { plugins {
id("cc-tweaked.vanilla") id("cc-tweaked.vanilla")
id("cc-tweaked.gametest") id("cc-tweaked.gametest")
id("cc-tweaked.illuaminate")
id("cc-tweaked.publishing") id("cc-tweaked.publishing")
} }
@@ -19,6 +18,10 @@ minecraft {
) )
} }
configurations {
register("cctJavadoc")
}
dependencies { dependencies {
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness. // Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
implementation(project(":core")) implementation(project(":core"))
@@ -41,4 +44,53 @@ dependencies {
testModImplementation(libs.bundles.kotlin) testModImplementation(libs.bundles.kotlin)
testFixturesImplementation(testFixtures(project(":core"))) 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.ChatFormatting;
import net.minecraft.Util; import net.minecraft.Util;
import net.minecraft.client.gui.GuiGraphics; 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.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory; 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); || 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 @Override
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) { protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {

View File

@@ -246,7 +246,7 @@ public class TerminalWidget extends AbstractWidget {
keysDown.clear(); keysDown.clear();
// When blurring, we should make the last mouse button go up // 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); computer.mouseUp(lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1);
lastMouseButton = -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_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. // Make all blocks aside from command computer mineable.
tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add( 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. * Add a new global metrics observer. This will receive metrics data for all computers.
* *
* @param tracker The observer to add. * @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) { synchronized (lock) {
if (trackers.contains(tracker)) return; if (trackers.contains(tracker)) return false;
trackers.add(tracker); trackers.add(tracker);
enabled = true; enabled = true;
return true;
} }
} }
@@ -59,11 +61,13 @@ public final class GlobalMetrics {
* Remove a previously-registered global metrics observer. * Remove a previously-registered global metrics observer.
* *
* @param tracker The observer to add. * @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) { synchronized (lock) {
trackers.remove(tracker); var changed = trackers.remove(tracker);
enabled = !trackers.isEmpty(); 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.ComputerMetricsObserver;
import dan200.computercraft.shared.computer.metrics.GlobalMetrics; import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
import javax.annotation.concurrent.GuardedBy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -21,29 +22,31 @@ import java.util.Map;
*/ */
public class BasicComputerMetricsObserver implements ComputerMetricsObserver { public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
private final GlobalMetrics owner; private final GlobalMetrics owner;
private boolean tracking = false;
@GuardedBy("this")
private final List<ComputerMetrics> timings = new ArrayList<>(); private final List<ComputerMetrics> timings = new ArrayList<>();
@GuardedBy("this")
private final Map<ServerComputer, ComputerMetrics> timingLookup = new MapMaker().weakKeys().makeMap(); private final Map<ServerComputer, ComputerMetrics> timingLookup = new MapMaker().weakKeys().makeMap();
public BasicComputerMetricsObserver(GlobalMetrics owner) { public BasicComputerMetricsObserver(GlobalMetrics owner) {
this.owner = owner; this.owner = owner;
} }
public synchronized void start() { public void start() {
if (!tracking) owner.addObserver(this); if (!owner.addObserver(this)) return;
tracking = true;
timings.clear(); synchronized (this) {
timingLookup.clear(); timings.clear();
timingLookup.clear();
}
} }
public synchronized boolean stop() { public boolean stop() {
if (!tracking) return false; if (!owner.removeObserver(this)) return false;
synchronized (this) {
owner.removeObserver(this); timingLookup.clear();
tracking = false; }
timingLookup.clear();
return true; return true;
} }
@@ -57,6 +60,7 @@ public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
return new ArrayList<>(timings); return new ArrayList<>(timings);
} }
@GuardedBy("this")
private ComputerMetrics getMetrics(ServerComputer computer) { private ComputerMetrics getMetrics(ServerComputer computer) {
var existing = timingLookup.get(computer); var existing = timingLookup.get(computer);
if (existing != null) return existing; 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.api.turtle.TurtleSide;
import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
import dan200.computercraft.shared.turtle.core.*; import dan200.computercraft.shared.turtle.core.*;
import java.util.Optional; import java.util.Optional;
@@ -749,7 +750,7 @@ public class TurtleAPI implements ILuaAPI {
* -- count = 13, * -- count = 13,
* -- } * -- }
* }</pre> * }</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 @LuaFunction
public final MethodResult getItemDetail(ILuaContext context, Optional<Integer> slot, Optional<Boolean> detailed) throws LuaException { 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. * A generic source of {@link LuaFunction} functions.
* <p> * <p>
* Unlike normal objects ({@link IDynamicLuaObject} or {@link IPeripheral}), methods do not target this object but * 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 * accept their target as the first parameter. This allows you to inject methods onto objects you do not own, as well as
* methods onto objects you do not own, as well as declaring methods for a specific "trait" (for instance, a Forge * declaring methods for a specific "trait" (for instance, a Forge capability or Fabric block lookup interface).
* capability or Fabric block lookup interface).
* <p> * <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 * 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 * determined by their id, rather than any peripheral provider, though additional types may be provided by overriding
* {@link GenericPeripheral#getType()}. * {@link GenericPeripheral#getType()}.
@@ -25,7 +24,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
* <pre>{@code * <pre>{@code
* public class InventoryMethods implements GenericSource { * public class InventoryMethods implements GenericSource {
* \@LuaFunction( mainThread = true ) * \@LuaFunction( mainThread = true )
* public static int size(IItemHandler inventory) { * public int size(IItemHandler inventory) {
* return inventory.getSlots(); * return inventory.getSlots();
* } * }
* *

View File

@@ -18,10 +18,7 @@ import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.reflect.Member; import java.lang.reflect.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
@@ -106,9 +103,14 @@ final class Generator<T> {
private final Function<MethodHandle, T> factory; private final Function<MethodHandle, T> factory;
private final Function<T, T> wrap; private final Function<T, T> wrap;
private final LoadingCache<Method, Optional<T>> methodCache = CacheBuilder private final LoadingCache<Method, Optional<T>> instanceCache = CacheBuilder
.newBuilder() .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) { Generator(List<Class<?>> context, Function<MethodHandle, T> factory, Function<T, T> wrap) {
this.context = context; this.context = context;
@@ -131,65 +133,94 @@ final class Generator<T> {
} }
} }
Optional<T> getMethod(Method method) { Optional<T> getInstanceMethod(Method method) {
return methodCache.getUnchecked(method); return instanceCache.getUnchecked(method);
} }
private Optional<T> build(Method method) { Optional<T> getGenericMethod(GenericMethod method) {
var name = method.getDeclaringClass().getName() + "." + method.getName(); return genericCache.getUnchecked(method);
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)) { * Check if a {@link LuaFunction}-annotated method can be used in this context.
LOG.warn("Lua Method {} should be final.", name); *
* @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)) { // Check we don't throw additional exceptions.
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);
var exceptions = method.getExceptionTypes(); var exceptions = method.getExceptionTypes();
for (var exception : exceptions) { for (var exception : exceptions) {
if (exception != LuaException.class) { if (exception != LuaException.class) {
LOG.error("Lua Method {} cannot throw {}.", name, exception.getName()); LOG.error("Lua Method {}.{} cannot throw {}.", method.getDeclaringClass().getName(), method.getName(), exception.getName());
return Optional.empty(); return false;
} }
} }
// unsafe can only be used on the computer thread, so reject it for mainThread functions.
var annotation = method.getAnnotation(LuaFunction.class); var annotation = method.getAnnotation(LuaFunction.class);
if (annotation.unsafe() && annotation.mainThread()) { if (annotation.unsafe() && annotation.mainThread()) {
LOG.error("Lua Method {} cannot use unsafe and mainThread", name); LOG.error("Lua Method {}.{} cannot use unsafe and mainThread.", method.getDeclaringClass().getName(), method.getName());
return Optional.empty(); return false;
} }
try { // Instance methods must be final - this prevents them being overridden and potentially exposed twice.
var originalHandle = LOOKUP.unreflect(method); var modifiers = method.getModifiers();
if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers) && !Modifier.isFinal(method.getDeclaringClass().getModifiers())) {
List<Type> parameters; LOG.warn("Lua Method {}.{} should be final.", method.getDeclaringClass().getName(), method.getName());
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();
} }
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. * @param unsafe Whether to allow unsafe argument getters.
* @return The wrapped method handle. * @return The wrapped method handle.
*/ */
@Nullable private @Nullable MethodHandle buildMethodHandle(Member method, MethodHandle handle, List<Type> parameterTypes, boolean unsafe) {
private MethodHandle buildMethodHandle(Member method, MethodHandle handle, List<Type> parameterTypes, boolean unsafe) {
if (handle.type().parameterCount() != parameterTypes.size() + 1) { if (handle.type().parameterCount() != parameterTypes.size() + 1) {
throw new IllegalArgumentException("Argument lists are mismatched"); throw new IllegalArgumentException("Argument lists are mismatched");
} }
@@ -263,8 +293,7 @@ final class Generator<T> {
} }
} }
@Nullable private static @Nullable MethodHandle loadArg(Member method, boolean unsafe, Class<?> argType, Type genericArg, int argIndex) {
private static MethodHandle loadArg(Member method, boolean unsafe, Class<?> argType, Type genericArg, int argIndex) {
if (argType == Coerced.class) { if (argType == Coerced.class) {
var klass = Reflect.getRawType(method, TypeToken.of(genericArg).resolveType(Reflect.COERCED_IN).getType(), false); var klass = Reflect.getRawType(method, TypeToken.of(genericArg).resolveType(Reflect.COERCED_IN).getType(), false);
if (klass == null) return null; if (klass == null) return null;
@@ -312,6 +341,22 @@ final class Generator<T> {
return null; 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") @SuppressWarnings("Guava")
static <T, U> com.google.common.base.Function<T, U> catching(Function<T, U> function, U def) { static <T, U> com.google.common.base.Function<T, U> catching(Function<T, U> function, U def) {
return x -> { return x -> {
@@ -320,7 +365,7 @@ final class Generator<T> {
} catch (Exception | LinkageError e) { } catch (Exception | LinkageError e) {
// LinkageError due to possible codegen bugs and NoClassDefFoundError. The latter occurs when fetching // 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. // 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; return def;
} }
}; };

View File

@@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays; import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -56,16 +55,11 @@ public final class GenericMethod {
Class<?> klass = source.getClass(); Class<?> klass = source.getClass();
var type = source instanceof GenericPeripheral generic ? generic.getType() : null; var type = source instanceof GenericPeripheral generic ? generic.getType() : null;
return Arrays.stream(klass.getDeclaredMethods()) return Arrays.stream(klass.getMethods())
.map(method -> { .map(method -> {
var annotation = method.getAnnotation(LuaFunction.class); var annotation = method.getAnnotation(LuaFunction.class);
if (annotation == null) return null; 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(); var types = method.getGenericParameterTypes();
if (types.length == 0) { if (types.length == 0) {
LOG.error("GenericSource method {}.{} has no parameters.", method.getDeclaringClass(), method.getName()); 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; continue;
} }
var instance = generator.getMethod(method).orElse(null); var instance = generator.getInstanceMethod(method).orElse(null);
if (instance == null) continue; if (instance == null) continue;
if (methods == null) methods = new ArrayList<>(); if (methods == null) methods = new ArrayList<>();
@@ -121,7 +121,7 @@ final class MethodSupplierImpl<T> implements MethodSupplier<T> {
for (var method : genericMethods) { for (var method : genericMethods) {
if (!method.target.isAssignableFrom(klass)) continue; 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 (instance == null) continue;
if (methods == null) methods = new ArrayList<>(); if (methods == null) methods = new ArrayList<>();

View File

@@ -35,6 +35,7 @@ local term = _ENV
-- @since 1.31 -- @since 1.31
-- @usage -- @usage
-- Redirect to a monitor on the right of the computer. -- Redirect to a monitor on the right of the computer.
--
-- term.redirect(peripheral.wrap("right")) -- term.redirect(peripheral.wrap("right"))
term.redirect = function(target) term.redirect = function(target)
expect(1, target, "table") 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 # New features in CC: Tweaked 1.109.0
* Update to Lua 5.2 * 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 * `math.random` now uses Lua 5.4's random number generator.
* `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.
Several bug fixes: Several bug fixes:
* Fix error when using position captures and backreferences in string patterns (e.g. `()(%1)`). * Fix errors involving `goto` statements having the wrong line number.
* Fix formatting non-real numbers with `%d`.
Type "help changelog" to see the full version history. Type "help changelog" to see the full version history.

View File

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

View File

@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream;
import static dan200.computercraft.test.core.ContramapMatcher.contramap; import static dan200.computercraft.test.core.ContramapMatcher.contramap;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@@ -30,7 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class GeneratorTest { public class GeneratorTest {
private static final MethodSupplierImpl<LuaMethod> GENERATOR = (MethodSupplierImpl<LuaMethod>) LuaMethodSupplier.create( 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 @Test
@@ -65,8 +66,10 @@ public class GeneratorTest {
} }
@Test @Test
public void testNonPublicClass() { public void testNonPublicClass() throws LuaException {
assertThat(GENERATOR.getMethods(NonPublic.class), is(empty())); var methods = GENERATOR.getMethods(NonPublic.class);
assertThat(methods, contains(named("go")));
assertThat(apply(methods, new NonPublic(), "go"), is(MethodResult.of()));
} }
@Test @Test
@@ -75,10 +78,18 @@ public class GeneratorTest {
} }
@Test @Test
public void testStaticMethod() throws LuaException { public void testStaticGenericMethod() throws LuaException {
var methods = GENERATOR.getMethods(StaticMethodTarget.class); var methods = GENERATOR.getMethods(GenericMethodTarget.class);
assertThat(methods, contains(named("go"))); assertThat(methods, hasItem(named("goStatic")));
assertThat(apply(methods, new StaticMethodTarget(), "go", "Hello", 123), is(MethodResult.of())); 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 @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 @Override
public String id() { public String id() {
return "source"; return "static";
} }
@LuaFunction @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 { dependencies {
clientCompileOnly(variantOf(libs.emi) { classifier("api") }) 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) { modCompileOnly(libs.bundles.externalMods.fabric.compile) {
exclude("net.fabricmc", "fabric-loader") exclude("net.fabricmc", "fabric-loader")
exclude("net.fabricmc.fabric-api") exclude("net.fabricmc.fabric-api")
@@ -72,9 +72,9 @@ dependencies {
include(libs.nightConfig.toml) include(libs.nightConfig.toml)
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness. // Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
api(commonClasses(project(":fabric-api"))) api(commonClasses(project(":fabric-api"))) { cct.exclude(this) }
clientApi(clientClasses(project(":fabric-api"))) clientApi(clientClasses(project(":fabric-api"))) { cct.exclude(this) }
implementation(project(":core")) 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 // 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. // in our POM, and this is the easiest way.
runtimeOnly(libs.cobalt) runtimeOnly(libs.cobalt)
@@ -168,7 +168,11 @@ loom {
configureForGameTest(this) configureForGameTest(this)
property("fabric-api.gametest") 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") runDir("run/gametest")
} }
} }
@@ -258,9 +262,7 @@ publishing {
publications { publications {
named("maven", MavenPublication::class) { named("maven", MavenPublication::class) {
mavenDependencies { mavenDependencies {
exclude(dependencies.create("cc.tweaked:")) cct.configureExcludes(this)
exclude(libs.jei.fabric.get())
exclude(libs.modmenu.get())
} }
} }
} }

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; package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.VanillaDetailRegistries; import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.platform.FabricContainerTransfer; import dan200.computercraft.shared.platform.FabricContainerTransfer;
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage; import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; 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; 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") @SuppressWarnings("UnstableApiUsage")
public class InventoryMethods implements GenericPeripheral { public final class InventoryMethods extends AbstractInventoryMethods<InventoryMethods.StorageWrapper> {
@Override
public PeripheralType getType() {
return PeripheralType.ofAdditional("inventory");
}
@Override
public String id() {
return ComputerCraftAPI.MOD_ID + ":inventory";
}
/** /**
* Wrapper over a {@link SlottedStorage}. * 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 * @param storage The underlying storage
*/ */
public record StorageWrapper(SlottedStorage<ItemVariant> storage) { public record StorageWrapper(SlottedStorage<ItemVariant> storage) {
} }
@Override
@LuaFunction(mainThread = true) @LuaFunction(mainThread = true)
public static int size(StorageWrapper inventory) { public int size(StorageWrapper inventory) {
return inventory.storage().getSlots().size(); return inventory.storage().getSlots().size();
} }
@Override
@LuaFunction(mainThread = true) @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<>(); Map<Integer, Map<String, ?>> result = new HashMap<>();
var slots = inventory.storage().getSlots(); var slots = inventory.storage().getSlots();
var size = slots.size(); var size = slots.size();
@@ -74,23 +64,26 @@ public class InventoryMethods implements GenericPeripheral {
return result; return result;
} }
@Override
@Nullable @Nullable
@LuaFunction(mainThread = true) @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)"); assertBetween(slot, 1, inventory.storage().getSlotCount(), "Slot out of range (%s)");
var stack = toStack(inventory.storage().getSlot(slot - 1)); var stack = toStack(inventory.storage().getSlot(slot - 1));
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack); return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
} }
@Override
@LuaFunction(mainThread = true) @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)"); assertBetween(slot, 1, inventory.storage().getSlotCount(), "Slot out of range (%s)");
return inventory.storage().getSlot(slot - 1).getCapacity(); return inventory.storage().getSlot(slot - 1).getCapacity();
} }
@Override
@LuaFunction(mainThread = true) @LuaFunction(mainThread = true)
public static int pushItems( public int pushItems(
StorageWrapper from, IComputerAccess computer, StorageWrapper from, IComputerAccess computer,
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException { ) throws LuaException {
@@ -112,8 +105,9 @@ public class InventoryMethods implements GenericPeripheral {
return moveItem(fromStorage, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit); return moveItem(fromStorage, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
} }
@Override
@LuaFunction(mainThread = true) @LuaFunction(mainThread = true)
public static int pullItems( public int pullItems(
StorageWrapper to, IComputerAccess computer, StorageWrapper to, IComputerAccess computer,
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException { ) throws LuaException {

View File

@@ -9,7 +9,6 @@ plugins {
id("cc-tweaked.forge") id("cc-tweaked.forge")
id("cc-tweaked.gametest") id("cc-tweaked.gametest")
alias(libs.plugins.mixinGradle) alias(libs.plugins.mixinGradle)
id("cc-tweaked.illuaminate")
id("cc-tweaked.mod-publishing") id("cc-tweaked.mod-publishing")
} }
@@ -117,7 +116,6 @@ mixin {
} }
configurations { configurations {
register("cctJavadoc")
minecraftLibrary { extendsFrom(minecraftEmbed.get()) } minecraftLibrary { extendsFrom(minecraftEmbed.get()) }
} }
@@ -133,9 +131,9 @@ dependencies {
libs.bundles.externalMods.forge.runtime.get().map { runtimeOnly(fg.deobf(it)) } libs.bundles.externalMods.forge.runtime.get().map { runtimeOnly(fg.deobf(it)) }
// Depend on our other projects. // Depend on our other projects.
api(commonClasses(project(":forge-api"))) api(commonClasses(project(":forge-api"))) { cct.exclude(this) }
api(clientClasses(project(":forge-api"))) clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) }
implementation(project(":core")) implementation(project(":core")) { cct.exclude(this) }
minecraftEmbed(libs.cobalt) { minecraftEmbed(libs.cobalt) {
jarJar.ranged(this, "[${libs.versions.cobalt.asProvider().get()},${libs.versions.cobalt.next.get()})") jarJar.ranged(this, "[${libs.versions.cobalt.asProvider().get()},${libs.versions.cobalt.next.get()})")
@@ -167,40 +165,10 @@ dependencies {
testModImplementation(testFixtures(project(":forge"))) testModImplementation(testFixtures(project(":forge")))
testFixturesImplementation(testFixtures(project(":core"))) testFixturesImplementation(testFixtures(project(":core")))
"cctJavadoc"(libs.cctJavadoc)
}
illuaminate {
version.set(libs.versions.illuaminate)
} }
// Compile tasks // 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 { tasks.processResources {
inputs.property("modVersion", modVersion) inputs.property("modVersion", modVersion)
inputs.property("forgeVersion", libs.versions.forge.get()) inputs.property("forgeVersion", libs.versions.forge.get())
@@ -240,24 +208,6 @@ tasks.test {
systemProperty("cct.test-files", layout.buildDirectory.dir("tmp/testFiles").getAbsolutePath()) 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) { val runGametest by tasks.registering(JavaExec::class) {
group = LifecycleBasePlugin.VERIFICATION_GROUP group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Runs tests on a temporary Minecraft instance." description = "Runs tests on a temporary Minecraft instance."
@@ -304,7 +254,7 @@ publishing {
artifact(tasks.jarJar) artifact(tasks.jarJar)
mavenDependencies { mavenDependencies {
exclude(dependencies.create("cc.tweaked:")) cct.configureExcludes(this)
exclude(libs.jei.forge.get()) 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; package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import net.minecraftforge.energy.IEnergyStorage; import net.minecraftforge.energy.IEnergyStorage;
/** /**
* Methods for interacting with blocks using Forge's energy storage system. * Fluid methods for Forge's {@link IEnergyStorage}.
* <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
*/ */
public class EnergyMethods implements GenericPeripheral { public final class EnergyMethods extends AbstractEnergyMethods<IEnergyStorage> {
@Override @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) @LuaFunction(mainThread = true)
public static int getEnergy(IEnergyStorage energy) { public int getEnergy(IEnergyStorage energy) {
return energy.getEnergyStored(); return energy.getEnergyStored();
} }
/** @Override
* 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) @LuaFunction(mainThread = true)
public static int getEnergyCapacity(IEnergyStorage energy) { public int getEnergyCapacity(IEnergyStorage energy) {
return energy.getMaxEnergyStored(); return energy.getMaxEnergyStored();
} }
} }

View File

@@ -4,13 +4,10 @@
package dan200.computercraft.shared.peripheral.generic.methods; package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.ForgeDetailRegistries; import dan200.computercraft.api.detail.ForgeDetailRegistries;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.platform.RegistryWrappers; import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.common.capabilities.ForgeCapabilities;
@@ -26,37 +23,12 @@ import java.util.Optional;
import static dan200.computercraft.shared.util.ArgumentHelpers.getRegistryEntry; import static dan200.computercraft.shared.util.ArgumentHelpers.getRegistryEntry;
/** /**
* Methods for interacting with tanks and other fluid storage blocks. * Fluid methods for Forge's {@link IFluidHandler}.
*
* @cc.module fluid_storage
* @cc.since 1.94.0
*/ */
public class FluidMethods implements GenericPeripheral { public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
@Override @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) @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<>(); Map<Integer, Map<String, ?>> result = new HashMap<>();
var size = fluids.getTanks(); var size = fluids.getTanks();
for (var i = 0; i < size; i++) { for (var i = 0; i < size; i++) {
@@ -67,24 +39,9 @@ public class FluidMethods implements GenericPeripheral {
return result; return result;
} }
/** @Override
* 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) @LuaFunction(mainThread = true)
public static int pushFluid( public int pushFluid(
IFluidHandler from, IComputerAccess computer, IFluidHandler from, IComputerAccess computer,
String toName, Optional<Integer> limit, Optional<String> fluidName String toName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException { ) throws LuaException {
@@ -107,24 +64,9 @@ public class FluidMethods implements GenericPeripheral {
: moveFluid(from, new FluidStack(fluid, actualLimit), to); : moveFluid(from, new FluidStack(fluid, actualLimit), to);
} }
/** @Override
* 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) @LuaFunction(mainThread = true)
public static int pullFluid( public int pullFluid(
IFluidHandler to, IComputerAccess computer, IFluidHandler to, IComputerAccess computer,
String fromName, Optional<Integer> limit, Optional<String> fluidName String fromName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException { ) throws LuaException {
@@ -194,7 +136,7 @@ public class FluidMethods implements GenericPeripheral {
* @return The amount of fluid moved. * @return The amount of fluid moved.
*/ */
private static int moveFluid(IFluidHandler from, FluidStack extracted, int limit, IFluidHandler to) { 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. // Limit the amount to extract.
extracted = extracted.copy(); extracted = extracted.copy();

View File

@@ -4,14 +4,10 @@
package dan200.computercraft.shared.peripheral.generic.methods; package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.VanillaDetailRegistries; import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.platform.ForgeContainerTransfer; import dan200.computercraft.shared.platform.ForgeContainerTransfer;
import net.minecraft.world.Container; import net.minecraft.world.Container;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
@@ -28,59 +24,18 @@ import java.util.Optional;
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween; import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
/** /**
* Methods for interacting with inventories. * Inventory methods for Forge's {@link IItemHandler}.
*
* @cc.module inventory
* @cc.since 1.94.0
*/ */
public class InventoryMethods implements GenericPeripheral { public final class InventoryMethods extends AbstractInventoryMethods<IItemHandler> {
@Override @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) @LuaFunction(mainThread = true)
public static int size(IItemHandler inventory) { public int size(IItemHandler inventory) {
return inventory.getSlots(); return inventory.getSlots();
} }
/** @Override
* 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) @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<>(); Map<Integer, Map<String, ?>> result = new HashMap<>();
var size = inventory.getSlots(); var size = inventory.getSlots();
for (var i = 0; i < size; i++) { for (var i = 0; i < size; i++) {
@@ -91,106 +46,26 @@ public class InventoryMethods implements GenericPeripheral {
return result; return result;
} }
/** @Override
* 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 @Nullable
@LuaFunction(mainThread = true) @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)"); assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
var stack = inventory.getStackInSlot(slot - 1); var stack = inventory.getStackInSlot(slot - 1);
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack); return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
} }
/** @Override
* 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) @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)"); assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
return inventory.getSlotLimit(slot - 1); return inventory.getSlotLimit(slot - 1);
} }
/** @Override
* 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) @LuaFunction(mainThread = true)
public static int pushItems( public int pushItems(
IItemHandler from, IComputerAccess computer, IItemHandler from, IComputerAccess computer,
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException { ) throws LuaException {
@@ -210,33 +85,9 @@ public class InventoryMethods implements GenericPeripheral {
return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit); return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
} }
/** @Override
* 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) @LuaFunction(mainThread = true)
public static int pullItems( public int pullItems(
IItemHandler to, IComputerAccess computer, IItemHandler to, IComputerAccess computer,
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException { ) throws LuaException {

View File

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

View File

@@ -30,12 +30,12 @@ The code for this is split into three separate components:
## Static content ## Static content
Rendering the static portion of the website is fortunately much simpler. 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 Javadoc on our peripherals and APIs to LDoc/[illuaminate] compatible documentation. This is then fed into illuaminate
which spits out HTML. which spits out HTML.
- `src/htmlTransform`: We do a small amount of post-processing on the HTML in order. This project does syntax - `src/htmlTransform`: We do a small amount of post-processing on the HTML, which is performed by this tool. This includes
highlighting of non-Lua code blocks, and replaces special `<mc-recipe>` tags with a rendered view of a given syntax highlighting of non-Lua code blocks, and replacing special `<mc-recipe>` tags with a rendered view of a given
Minecraft recipe. Minecraft recipe.
[TeaVM]: https://github.com/konsoletyper/teavm "TeaVM - Compiler of Java bytecode to JavaScript" [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 // Sources
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs") inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom") 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 // Assets
inputs.files(rollup) inputs.files(rollup)

View File

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

View File

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