1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-17 15:07:38 +00:00

Compare commits

..

26 Commits

Author SHA1 Message Date
Jonathan Coates
bcb3e9bd53 Bump CC:T to 1.108.4 2023-10-29 12:02:11 +00:00
Jonathan Coates
c30bffbd0f Add additional tests for filesystems/mounts
This tries to cover some holes in our existing coverage.

 - Port some of our Java readable handle tests to Lua (and also clean up
   the Java versions to stop using ObjectWrapper - that dates to
   pre-@LuaFunction!)

 - Test a couple of discrepancies between binary and text handles. This
   is mostly to do with the original number-based .read() and .write()
   interface for binary handles.

 - Fix a couple of edge cases in file-size accounting.
2023-10-29 12:01:26 +00:00
Jonathan Coates
91c41856c5 Add an "Incompatibilities between versions" page
This is largely based on our existing wiki page. I've pruned out a
couple of entries which I think are largely irrelevant (config file
splitting, Java API changes).

10/10 job by me of changing nothing since 1.13. Shame to break that
streak really.
2023-10-28 20:00:56 +01:00
Jonathan Coates
18c9723308 Add a standalone CC:T UI
Does it count as an emulator when it's official? I hope not, as this'd
make it my fourth or fifth emulator at this point.

 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Developing/debugging CraftOS is a massive pain to do inside Minecraft,
as any change to resources requires a compile+hot swap cycle (and
sometimes a `/reload` in-game). As such, it's often more convenient to
spin up an emulator, pointing it to load the ROM from CC:T's sources.

However, this isn't practical when also making changes to the Java
classes. In this case, we either need to go in-game, or build a custom
version of CCEmuX.

This commit offers an alternative option: we now have our own emulator,
which allows us to hot swap both Lua and Java to our heart's content.

Most of the code here is based on our monitor TBO renderer. We probably
could share some more of this, but there's not really a good place for
it - feels a bit weird just to chuck it in :core.

This is *not* a general-purpose emulator. It's limited in a lot of
ways (won't launch on Mac[^1], no support for multiple computers) - just
stick to what's there already.

[^1]: We require OpenGL 4.5 due to our use of DSA.
2023-10-28 17:58:11 +01:00
Jonathan Coates
aee382ed70 Replace Fabric JUnit hacks with fabric-loader-junit
Also configure some of our common JUnit run configurations via Gradle -
I end up setting these up in every worktree anyway - so let's just do it
once.
2023-10-26 22:06:40 +01:00
Jonathan Coates
6656da5877 Remove disable_lua51_features config option
In practice, we're never going to change this to true by default. The
old Tekkit Legends pack enabled this[^1], and that caused a lot of
problems, though admittedly back in 2016 so things might be better now.

If people do want this functionality, it should be fairly easy to
replicate with a datapack, adding a file to rom/autorun.

[^1]: See https://www.computercraft.info/forums2/index.php?/topic/27663-

      Hate that I remember this, why is this still in my brain?
2023-10-25 08:59:55 +01:00
Jonathan Coates
09e521727f Make mount error messages a bit more consistent
- Move most error message constants to a new MountHelpers class.
 - Be a little more consistent in when we throw "No such file" vs "Not a
   file/directory" messages.
2023-10-22 13:13:07 +01:00
Jonathan Coates
cab66a2d6e Replace Collections methods with {List,Map,Set}.of
The two implementations aren't entirely compatible - the implementation
returned by .of will throw an NPE on .contains(null), whereas the
Collections implementations just return false. However, we try to avoid
passing null to collections methods, so this should be safe.

There's no strong reason to do this, but it helps make the code a little
more consistent
2023-10-21 10:37:43 +01:00
Jonathan Coates
8eabd4f303 Fix signs being empty when placed
As of 1.20, sign messages are immutable - we need to do
text = text.setMesssage(...) instead. Also do a tiny bit of cleanup to
this function while we're here.

Probably not the best use of my lunch break :D:.

Fixes #1611.
2023-10-20 13:32:38 +01:00
Jonathan Coates
e3ced84885 Merge branch 'feature/split-computer-thread' into mc-1.20.x 2023-10-19 22:54:56 +01:00
Jonathan Coates
0929ab577d Split ComputerThread/ComputerExecutor up a little
This is an attempt to enforce better separation between ComputerThread
and ComputerExecutor. Both of these classes are pretty complex in their
own right, and the way the two bleed into each other makes it all the
more confusing!

This effectively splits the ComputerExecutor into two separate classes:
 - ComputerScheduler.Executor (with the actual implementation inside
   ComputerThread): This holds all the ComputerThread-related logic
   which used to be in ComputerExecutor, including:

    - before/after work hooks
    - is-on-thread tracking
    - virtual runtime computation

 - ComputerScheduler.Worker: This encapsulates all the computer-related
   behaviour. The actual implementation remains in ComputerExecutor.

The boundaries are still a little fuzzy here, and it's all definitely
more coupled then I'd like, but still an improvement!

There are several additional changes at the same time:

 - TimeoutState has also been split up, to better define the boundary
   between consumers (such as ComputerExecutor and ILuaMachine) and
   controllers (ComputerThread).

   The getters still live in TimeoutState, but the core logic lives in
   ManagedTimeoutState.

 - We no longer track cumulative time in the TimeoutState. Instead, we
   allow varying the timeout of a computer. When a computer is paused,
   we store the remaining time, and restore it when resuming again.

   This also allows us give a longer timeout for computer
   startup/shutdown, hopefully avoiding some of those class-not-found
   issues we've seen.

 - We try to make the state machine of how ComputerExecutors live on the
   queue a little more explicit. This is very messy/confusing -
   something I want to property test in the future.

I'm sure there's more to be done here, especially in ComputerExecutor,
but hopefully this makes future changes a little less intimidating.
2023-10-19 22:50:11 +01:00
Jonathan Coates
2228733abc Move ComputerThread to its own package
This is entirely broken - we rely a lot on package locals right now -
but makes the next commit a little cleaner.
2023-10-19 18:31:02 +01:00
Jonathan Coates
e67c94d1bd Fix a couple of future deprecations in Gradle 2023-10-19 18:28:15 +01:00
Jonathan Coates
ae5a661a47 Add a discarding MetricsObserver
This is useful for test code where we don't care about the metrics
gathered.
2023-10-19 18:27:58 +01:00
Jonathan Coates
0ff58cdc3e Unify the generic peirpheral system a litte
Allows registering arbitrary block lookup functions instead of a
platform-specific capability. This is roughly what Fabric did before,
but generalised to also take an invalidation callback.

This callback is a little nasty - it needs to be a NonNullableConsumer
on Forge, but that class isn't available on Fabric. For now, we make the
lookup function (and thus the generic peripheral provider) generic on
some <T extends Runnable> type, then specialise that on the Forge side.
Hopefully we can clean this up when NeoForge reworks capabilities.
2023-10-17 21:59:16 +01:00
Jonathan Coates
1747c74770 Relicense some translations
One issue with Weblate is tracking down the provenance of who has
touched what file. I'm fairly sure this is accurate though.
2023-10-16 22:12:41 +01:00
Jonathan Coates
71669cf49c Replace ASM generation with MethodHandles
This is the second time I've rewritten our class generation in a little
over a month. Oh dear!

Back in d562a051c7 we started using method
handles inside our generated ASM, effectively replacing a direct call
with .invokeExact on a constant method handle.

This goes one step further and removes our ASM entirely, building up a
MethodHandle that checks arguments and then wraps the return value.
Rather than generating a class, we just return a new LuaFunction
instance that invokeExacts the method handle.

This is definitely slower than what we had before, but in the order of
8ns vs 12ns (in the worst case, sometimes they're much more comparable),
so I'm not too worried in practice.

However, generation of the actual method is now a bit faster. I've not
done any proper benchmarking, but it's about 20-30% faster.

This also gives us a bit more flexibility in the future, for instance
uisng bound MethodHandles in generation (e.g. for instance methods on
GenericSources). Not something I'm planning on doing right now, but is
an option.
2023-10-11 20:05:37 +01:00
Jonathan Coates
bd327e37eb Fix common jar not actually being published
Or rather, being published to the wrong place. The java-convention
plugin sets the group, but that was applied after the publishing one - I
was hoping it'd read that property lazy, but clearly not!
2023-10-11 19:15:36 +01:00
Jonathan Coates
bdce9a8170 Replace several Guava classes with Java stdlib
Wow, some of this is /old/. All the Maps.newHashMap stuff dates back to
Java 6, so must originally be CCTweaks code?!

We're unlikely to drop our Guava dependency (we use too much other
stuff), but we should make the most of the stdlib where possible.
2023-10-11 09:59:55 +01:00
Jonathan Coates
7e5598d084 Use ClassValue instead of LoadingCache
This should be significantly faster than LoadingCache (2.5x in my
benchmarks, but not sure they're representative). This isn't super
important - a lookup only takes 6us - but still worth using!
2023-10-11 09:30:29 +01:00
Jonathan Coates
440fca6535 Relicense a couple of more files
We've got in touch with Brady, so can now do the last of the docs \o/!

Also pick up a couple of stragglers that I'd missed from before.
2023-10-11 08:00:07 +01:00
Weblate
6635edd35c Translations for French
Translations for German

Translations for German

Co-authored-by: Sammy <SammyKoch@pm.me>
Co-authored-by: SquidDev <git@squiddev.cc>
2023-10-09 22:33:03 +00:00
Jonathan Coates
93ad40efbb Ensure the terminal exists when creating a monitor peripheral
Previously we had the invariant that if we had a server monitor, we also
had a terminal. When a monitor shrank into a place, we deleted the
monitor, and then recreated it when a peripheral was requested.

As of ab785a0906 this has changed
slightly, and we now just delete the terminal (keeping the ServerMonitor
around). However, we didn't adjust the peripheral code accordingly,
meaning we didn't recreate the /terminal/ when a peripheral was
requested.

The fix for this is very simple - most of the rest of this commit is
some additional code for ensuring monitor invariants hold, so we can
write tests with a little more confidence.

I'm not 100% sold on this approach. It's tricky having a double layer of
nullable state (ServerMonitor, and then the terminal). However, I think
this is reasonable - the ServerMonitor is a reference to the multiblock,
and the Terminal is part of the multiblock's state.

Even after all the refactors, monitor code is still nastier than I'd
like :/.

Fixes #1608
2023-10-09 22:09:01 +01:00
Jonathan Coates
27dc8b5b2c Pass follow_redirects flag to the CORS proxy
Currently redirects would be returned from the proxy, and then
immediately followed by XMLHTTPRequest. The proxy now follows requests
(when requested), so that should no longer happen.

We should probably switch over to fetch(...) here, to allow setting
follow_redirects to false, but that's a job for another day.

Haha, so many web emulator related commits of late. This'll die down
soon.
2023-10-08 20:12:47 +01:00
Jonathan Coates
3ebdf7ef5e Bump CC:T to 1.108.3 2023-10-08 15:25:45 +01:00
Jonathan Coates
905d4cb091 Fix crash when joining a dedicated server
We can't use FriendlyByte.readCollection to read to a
pre-allocated/array-backed NonNullList, as that doesn't implement
List.add. Instead, we just need to do a normal loop.

We add a couple of tests to round-trip our recipe specs. Unfortunately
we can't test the recipes themselves as our own registries aren't set
up, so this'll have to do for now.
2023-10-08 15:22:32 +01:00
181 changed files with 4043 additions and 1812 deletions

View File

@@ -53,6 +53,7 @@ Files:
projects/common/src/main/resources/assets/computercraft/textures/*
projects/common/src/main/resources/pack.mcmeta
projects/common/src/main/resources/pack.png
projects/core/src/main/resources/assets/computercraft/textures/gui/term_font.png
projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme
projects/core/src/main/resources/data/computercraft/lua/rom/help/*
projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/*
@@ -62,11 +63,23 @@ Copyright: 2011 Daniel Ratcliffe
License: LicenseRef-CCPL
Files:
projects/common/src/main/resources/assets/computercraft/lang/*
projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json
projects/common/src/main/resources/assets/computercraft/lang/ko_kr.json
projects/common/src/main/resources/assets/computercraft/lang/pl_pl.json
projects/common/src/main/resources/assets/computercraft/lang/pt_br.json
projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json
projects/common/src/main/resources/assets/computercraft/lang/uk_ua.json
projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json
Comment: Community-contributed license files
Copyright: 2017 The CC: Tweaked Developers
License: LicenseRef-CCPL
Files:
projects/common/src/main/resources/assets/computercraft/lang/*
Comment: Community-contributed license files
Copyright: 2017 The CC: Tweaked Developers
License: MPL-2.0
Files:
.github/*
Comment:

View File

@@ -2,7 +2,11 @@
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.JUnitExt
import net.fabricmc.loom.api.LoomGradleExtensionAPI
import net.fabricmc.loom.util.gradle.SourceSetHelper
import org.jetbrains.gradle.ext.compiler
import org.jetbrains.gradle.ext.runConfigurations
import org.jetbrains.gradle.ext.settings
plugins {
@@ -38,6 +42,50 @@ githubRelease {
tasks.publish { dependsOn(tasks.githubRelease) }
idea.project.settings.runConfigurations {
register<JUnitExt>("Core Tests") {
vmParameters = "-ea"
moduleName = "${idea.project.name}.core.test"
packageName = ""
}
register<JUnitExt>("CraftOS Tests") {
vmParameters = "-ea"
moduleName = "${idea.project.name}.core.test"
className = "dan200.computercraft.core.ComputerTestDelegate"
}
register<JUnitExt>("CraftOS Tests (Fast)") {
vmParameters = "-ea -Dcc.skip_keywords=slow"
moduleName = "${idea.project.name}.core.test"
className = "dan200.computercraft.core.ComputerTestDelegate"
}
register<JUnitExt>("Common Tests") {
vmParameters = "-ea"
moduleName = "${idea.project.name}.common.test"
packageName = ""
}
register<JUnitExt>("Fabric Tests") {
val fabricProject = evaluationDependsOn(":fabric")
val classPathGroup = fabricProject.extensions.getByType<LoomGradleExtensionAPI>().mods
.joinToString(File.pathSeparator + File.pathSeparator) { modSettings ->
SourceSetHelper.getClasspath(modSettings, project).joinToString(File.pathSeparator) { it.absolutePath }
}
vmParameters = "-ea -Dfabric.classPathGroups=$classPathGroup"
moduleName = "${idea.project.name}.fabric.test"
packageName = ""
}
register<JUnitExt>("Forge Tests") {
vmParameters = "-ea"
moduleName = "${idea.project.name}.forge.test"
packageName = ""
}
}
idea.project.settings.compiler.javac {
// We want ErrorProne to be present when compiling via IntelliJ, as it offers some helpful warnings
// and errors. Loop through our source sets and find the appropriate flags.

View File

@@ -50,10 +50,11 @@ dependencies {
implementation(libs.curseForgeGradle)
implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.ideaExt)
implementation(libs.librarian)
implementation(libs.minotaur)
implementation(libs.vineflower)
implementation(libs.vanillaGradle)
implementation(libs.vineflower)
}
gradlePlugin {

View File

@@ -173,7 +173,7 @@ abstract class CCTweakedExtension(
}
fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
val classDump = project.buildDir.resolve("jacocoClassDump/${task.name}")
val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}")
val reportTaskName = "jacoco${task.name.capitalized()}Report"
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
@@ -185,7 +185,7 @@ abstract class CCTweakedExtension(
jacoco.applyTo(this)
extensions.configure(JacocoTaskExtension::class.java) {
includes = listOf("dan200.computercraft.*")
classDumpDir = classDump
classDumpDir = classDump.get().asFile
// Older versions of modlauncher don't include a protection domain (and thus no code
// source). Jacoco skips such classes by default, so we need to explicitly include them.

View File

@@ -9,6 +9,10 @@ import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.plugins.ide.idea.model.IdeaModel
import org.jetbrains.gradle.ext.IdeaExtPlugin
import org.jetbrains.gradle.ext.runConfigurations
import org.jetbrains.gradle.ext.settings
/**
* Configures projects to match a shared configuration.
@@ -21,6 +25,20 @@ class CCTweakedPlugin : Plugin<Project> {
val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main")))
}
project.plugins.withType(IdeaExtPlugin::class.java) { extendIdea(project) }
}
/**
* Extend the [IdeaExtPlugin] plugin's `runConfiguration` container to also support [JUnitExt].
*/
private fun extendIdea(project: Project) {
val ideaModel = project.extensions.findByName("idea") as IdeaModel? ?: return
val ideaProject = ideaModel.project ?: return
ideaProject.settings.runConfigurations {
registerFactory(JUnitExt::class.java) { name -> project.objects.newInstance(JUnitExt::class.java, name) }
}
}
companion object {

View File

@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import org.jetbrains.gradle.ext.JUnit
import javax.inject.Inject
/**
* A version of [JUnit] with a functional [className].
*
* See [#92](https://github.com/JetBrains/gradle-idea-ext-plugin/issues/92).
*/
open class JUnitExt @Inject constructor(nameParam: String) : JUnit(nameParam) {
override fun toMap(): MutableMap<String, *> {
val map = HashMap(super.toMap())
// Should be "class" instead of "className".
// See https://github.com/JetBrains/intellij-community/blob/9ba394021dc73a3926f13d6d6cdf434f9ee7046d/plugins/junit/src/com/intellij/execution/junit/JUnitRunConfigurationImporter.kt#L39
map["class"] = className
return map
}
}

View File

@@ -58,7 +58,7 @@ abstract class ClientJavaExec : JavaExec() {
if (!clientDebug) systemProperty("cctest.client", "")
if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
workingDir(project.buildDir.resolve("gametest").resolve(name))
workingDir(project.layout.buildDirectory.dir("gametest/$name"))
}
init {

View File

@@ -112,7 +112,9 @@ SPDX-License-Identifier: MPL-2.0
<module name="LambdaParameterName" />
<module name="LocalFinalVariableName" />
<module name="LocalVariableName" />
<module name="MemberName" />
<module name="MemberName">
<property name="format" value="^\$?[a-z][a-zA-Z0-9]*$" />
</module>
<module name="MethodName">
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
</module>
@@ -122,7 +124,7 @@ SPDX-License-Identifier: MPL-2.0
</module>
<module name="ParameterName" />
<module name="StaticVariableName">
<property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
<property name="format" value="^[a-z][a-zA-Z0-9]*$" />
</module>
<module name="TypeName" />

View File

@@ -6,7 +6,7 @@ see: key To listen to any key press.
<!--
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: LicenseRef-CCPL
SPDX-License-Identifier: MPL-2.0
-->
The [`char`] event is fired when a character is typed on the keyboard.

View File

@@ -5,7 +5,7 @@ module: [kind=event] key
<!--
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: LicenseRef-CCPL
SPDX-License-Identifier: MPL-2.0
-->
This event is fired when any key is pressed while the terminal is focused.

View File

@@ -6,7 +6,7 @@ see: keys For a lookup table of the given keys.
<!--
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: LicenseRef-CCPL
SPDX-License-Identifier: MPL-2.0
-->
Fired whenever a key is released (or the terminal is closed while a key was being pressed).

View File

@@ -5,7 +5,7 @@ module: [kind=event] mouse_click
<!--
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: LicenseRef-CCPL
SPDX-License-Identifier: MPL-2.0
-->
This event is fired when the terminal is clicked with a mouse. This event is only fired on advanced computers (including

View File

@@ -6,7 +6,7 @@ see: mouse_click For when a mouse button is initially pressed.
<!--
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: LicenseRef-CCPL
SPDX-License-Identifier: MPL-2.0
-->
This event is fired every time the mouse is moved while a mouse button is being held.

View File

@@ -5,7 +5,7 @@ module: [kind=event] mouse_scroll
<!--
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: LicenseRef-CCPL
SPDX-License-Identifier: MPL-2.0
-->
This event is fired when a mouse wheel is scrolled in the terminal.

View File

@@ -5,7 +5,7 @@ module: [kind=event] mouse_up
<!--
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: LicenseRef-CCPL
SPDX-License-Identifier: MPL-2.0
-->
This event is fired when a mouse button is released or a held mouse leaves the computer's terminal.

View File

@@ -0,0 +1,67 @@
---
module: [kind=reference] breaking_changes
---
<!--
SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
# Incompatibilities between versions
CC: Tweaked tries to remain as compatible between versions as possible, meaning most programs written for older version
of the mod should run fine on later versions.
> [External peripherals][!WARNING]
>
> While CC: Tweaked is relatively stable across versions, this may not be true for other mods which add their own
> peripherals. Older programs which interact with external blocks may not work on newer versions of the game.
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.
## Minecraft 1.13 {#mc-1.13}
- The "key code" for [`key`] and [`key_up`] events has changed, due to Minecraft updating to LWJGL 3. Make sure you're
using the constants provided by the [`keys`] API, rather than hard-coding numerical values.
Related to this change, the numpad enter key now has a different key code to the enter key. You may need to adjust
your programs to handle both. (Note, the `keys.numpadEnter` constant was defined in pre-1.13 versions of CC, but the
`keys.enter` constant was queued when the key was pressed)
- Minecraft 1.13 removed the concept of item damage and block metadata (see ["The Flattening"][flattening]). As a
result `turtle.inspect` no longer provides block metadata, and `turtle.getItemDetail` no longer provides damage.
- Block states (`turtle.inspect().state`) should provide all the same information as block metadata, but in a much
more understandable format.
- Item and block names now represent a unique item type. For instance, wool is split into 16 separate items
(`minecraft:white_wool`, etc...) rather than a single `minecraft:wool` with each meta/damage value specifying the
colour.
- Custom ROMs are now provided using data packs rather than resource packs. This should mostly be a matter of renaming
the "assets" folder to "data", and placing it in "datapacks", but there are a couple of other gotchas to look out
for:
- Data packs [impose some restrictions on file names][legal_data_pack]. As a result, your programs and directories
must all be lower case.
- Due to how data packs are read by CC: Tweaked, you may need to use the `/reload` command to see changes to your
pack show up on the computer.
See [the example datapack][datapack-example] for how to get started.
- Turtles can now be waterlogged and move "through" water sources rather than breaking them.
## CC: Tweaked 1.88.0 {#cc-1.88}
- Unlabelled computers and turtles now keep their ID when broken, meaning that unlabelled computers/items do not stack.
## ComputerCraft 1.80pr1 {#cc-1.80}
- Programs run via `shell.run` are now started in their own isolated environment. This means globals set by programs
will not be accessible outside of this program.
- Programs containing `/` are looked up in the current directory and are no longer looked up on the path. For instance,
you can no longer type `turtle/excavate` to run `/rom/programs/turtle/excavate.lua`.
[flattening]: https://minecraft.wiki.com/w/Java_Edition_1.13/Flattening
[legal_data_pack]: https://minecraft.gamepedia.com/Tutorials/Creating_a_data_pack#Legal_characters
[datapack-example]: https://github.com/cc-tweaked/datapack-example "An example datapack for CC: Tweaked"

View File

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

View File

@@ -21,6 +21,7 @@ autoService = "1.1.1"
checkerFramework = "3.32.0"
cobalt = "0.7.3"
cobalt-next = "0.7.4" # Not a real version, used to constrain the version we accept.
commonsCli = "1.3.1"
fastutil = "8.5.9"
guava = "31.1-jre"
jetbrainsAnnotations = "24.0.1"
@@ -45,7 +46,6 @@ rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
# Testing
byteBuddy = "1.14.7"
hamcrest = "2.2"
jqwik = "1.7.4"
junit = "5.10.0"
@@ -62,14 +62,15 @@ githubRelease = "2.4.1"
ideaExt = "1.1.7"
illuaminate = "0.1.0-44-g9ee0055"
librarian = "1.+"
lwjgl = "3.3.1"
minotaur = "2.+"
mixinGradle = "0.7.+"
nullAway = "0.9.9"
spotless = "6.21.0"
taskTree = "2.1.1"
teavm = "0.9.0-SQUID.1"
vanillaGradle = "0.2.1-SNAPSHOT"
vineflower = "1.11.0"
teavm = "0.9.0-SQUID.1"
[libraries]
# Normal dependencies
@@ -78,6 +79,7 @@ asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
cobalt = { module = "org.squiddev:Cobalt", version.ref = "cobalt" }
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
@@ -97,6 +99,7 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
# Minecraft mods
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
@@ -114,8 +117,6 @@ rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
# Testing
byteBuddyAgent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byteBuddy" }
byteBuddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byteBuddy" }
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
@@ -124,6 +125,12 @@ junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", vers
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
# LWJGL
lwjgl-bom = { module = "org.lwjgl:lwjgl-bom", version.ref = "lwjgl" }
lwjgl-core = { module = "org.lwjgl:lwjgl" }
lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl" }
lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw" }
# Build tools
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
@@ -135,6 +142,7 @@ errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", versi
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
@@ -154,7 +162,6 @@ vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vinef
[plugins]
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }

View File

@@ -2,7 +2,7 @@
; SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
;
; SPDX-License-Identifier: LicenseRef-CCPL
; SPDX-License-Identifier: MPL-2.0
(sources
/doc/
@@ -77,7 +77,6 @@
(globals
:max
_CC_DEFAULT_SETTINGS
_CC_DISABLE_LUA51_FEATURES
_HOST
;; Ideally we'd pick these up from bios.lua, but illuaminate currently
;; isn't smart enough.

View File

@@ -62,6 +62,9 @@ mentioning:
- `lints`: This defines an [ErrorProne] plugin which adds a couple of compile-time checks to our code. This is what
enforces that no client-specific code is used inside the `main` source set (and a couple of other things!).
- `standalone`: This contains a standalone UI for computers, allowing debugging and development of CraftOS without
launching Minecraft.
- `web`: This contains the additional tooling for building [the documentation website][tweaked.cc], such as support for
rendering recipes

View File

@@ -19,8 +19,6 @@ import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.*;
@@ -36,8 +34,6 @@ import java.util.function.Function;
* @param <R> The upgrade serialiser to register for.
*/
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
private static final Logger LOGGER = LogManager.getLogger();
private final PackOutput output;
private final String name;
private final String folder;

View File

@@ -7,9 +7,9 @@ import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
plugins {
id("cc-tweaked.publishing")
id("cc-tweaked.vanilla")
id("cc-tweaked.gametest")
id("cc-tweaked.publishing")
}
minecraft {
@@ -39,4 +39,6 @@ dependencies {
testModImplementation(testFixtures(project(":core")))
testModImplementation(testFixtures(project(":common")))
testModImplementation(libs.bundles.kotlin)
testFixturesImplementation(testFixtures(project(":core")))
}

View File

@@ -33,7 +33,6 @@ import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -219,7 +218,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
private void alert(Component title, Component message) {
OptionScreen.show(minecraft, title, message,
Collections.singletonList(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
List.of(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
() -> minecraft.setScreen(this)
);
}

View File

@@ -14,10 +14,10 @@ import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.core.util.Colour;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.server.packs.resources.ResourceProvider;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL31;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
@@ -36,12 +36,12 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.get
* @see RenderTypes#getMonitorTextureBufferShader()
*/
public class MonitorTextureBufferShader extends ShaderInstance {
private static final Logger LOG = LoggerFactory.getLogger(MonitorTextureBufferShader.class);
public static final int UNIFORM_SIZE = 4 * 4 * 16 + 4 + 4 + 2 * 4 + 4;
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
private static final Logger LOGGER = LogManager.getLogger();
private final int monitorData;
private int uniformBuffer = 0;
@@ -75,7 +75,7 @@ public class MonitorTextureBufferShader extends ShaderInstance {
private Uniform getUniformChecked(String name) {
var uniform = getUniform(name);
if (uniform == null) {
LOGGER.warn("Monitor shader {} should have uniform {}, but it was not present.", getName(), name);
LOG.warn("Monitor shader {} should have uniform {}, but it was not present.", getName(), name);
}
return uniform;

View File

@@ -213,7 +213,6 @@ public final class LanguageProvider implements DataProvider {
addConfigEntry(ConfigSpec.floppySpaceLimit, "Floppy Disk space limit (bytes)");
addConfigEntry(ConfigSpec.uploadMaxSize, "File upload size limit (bytes)");
addConfigEntry(ConfigSpec.maximumFilesOpen, "Maximum files open per computer");
addConfigEntry(ConfigSpec.disableLua51Features, "Disable Lua 5.1 features");
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");

View File

@@ -18,8 +18,8 @@ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.item.ItemStack;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.Collection;
@@ -37,7 +37,7 @@ import java.util.stream.Collectors;
* @see PocketUpgrades
*/
public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
private static final Logger LOGGER = LogManager.getLogger();
private static final Logger LOG = LoggerFactory.getLogger(UpgradeManager.class);
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
public record UpgradeWrapper<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase>(
@@ -48,8 +48,8 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
private final String kind;
private final ResourceKey<Registry<R>> registry;
private Map<String, UpgradeWrapper<R, T>> current = Collections.emptyMap();
private Map<T, UpgradeWrapper<R, T>> currentWrappers = Collections.emptyMap();
private Map<String, UpgradeWrapper<R, T>> current = Map.of();
private Map<T, UpgradeWrapper<R, T>> currentWrappers = Map.of();
public UpgradeManager(String kind, String path, ResourceKey<Registry<R>> registry) {
super(GSON, path);
@@ -103,13 +103,13 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
try {
loadUpgrade(newUpgrades, element.getKey(), element.getValue());
} catch (IllegalArgumentException | JsonParseException e) {
LOGGER.error("Error loading {} {} from JSON file", kind, element.getKey(), e);
LOG.error("Error loading {} {} from JSON file", kind, element.getKey(), e);
}
}
current = Collections.unmodifiableMap(newUpgrades);
currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x));
LOGGER.info("Loaded {} {}s", current.size(), kind);
LOG.info("Loaded {} {}s", current.size(), kind);
}
private void loadUpgrade(Map<String, UpgradeWrapper<R, T>> current, ResourceLocation id, JsonElement json) {

View File

@@ -12,7 +12,7 @@ import java.util.HashMap;
import java.util.Map;
final class WiredNetworkChangeImpl implements WiredNetworkChange {
private static final WiredNetworkChangeImpl EMPTY = new WiredNetworkChangeImpl(Collections.emptyMap(), Collections.emptyMap());
private static final WiredNetworkChangeImpl EMPTY = new WiredNetworkChangeImpl(Map.of(), Map.of());
private final Map<String, IPeripheral> removed;
private final Map<String, IPeripheral> added;
@@ -27,11 +27,11 @@ final class WiredNetworkChangeImpl implements WiredNetworkChange {
}
static WiredNetworkChangeImpl added(Map<String, IPeripheral> added) {
return added.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Collections.emptyMap(), Collections.unmodifiableMap(added));
return added.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Map.of(), Collections.unmodifiableMap(added));
}
static WiredNetworkChangeImpl removed(Map<String, IPeripheral> removed) {
return removed.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Collections.unmodifiableMap(removed), Collections.emptyMap());
return removed.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Collections.unmodifiableMap(removed), Map.of());
}
static WiredNetworkChangeImpl changeOf(Map<String, IPeripheral> oldPeripherals, Map<String, IPeripheral> newPeripherals) {
@@ -39,9 +39,9 @@ final class WiredNetworkChangeImpl implements WiredNetworkChange {
if (oldPeripherals.isEmpty() && newPeripherals.isEmpty()) {
return EMPTY;
} else if (oldPeripherals.isEmpty()) {
return new WiredNetworkChangeImpl(Collections.emptyMap(), newPeripherals);
return new WiredNetworkChangeImpl(Map.of(), newPeripherals);
} else if (newPeripherals.isEmpty()) {
return new WiredNetworkChangeImpl(oldPeripherals, Collections.emptyMap());
return new WiredNetworkChangeImpl(oldPeripherals, Map.of());
}
Map<String, IPeripheral> added = new HashMap<>(newPeripherals);

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.impl.network.wired;
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.api.network.Packet;
import dan200.computercraft.api.network.wired.WiredNetwork;
import dan200.computercraft.api.network.wired.WiredNode;
@@ -57,10 +56,10 @@ final class WiredNetworkImpl implements WiredNetwork {
// Move all nodes across into this network, destroying the original nodes.
nodes.addAll(otherNodes);
for (var node : otherNodes) node.network = this;
other.nodes = Collections.emptySet();
other.nodes = Set.of();
// Move all peripherals across,
other.peripherals = Collections.emptyMap();
other.peripherals = Map.of();
peripherals.putAll(otherPeripherals);
if (!thisPeripherals.isEmpty()) {
@@ -217,7 +216,7 @@ final class WiredNetworkImpl implements WiredNetwork {
try {
// We special case the original node: detaching all peripherals when needed.
wired.network = wiredNetwork;
wired.peripherals = Collections.emptyMap();
wired.peripherals = Map.of();
// Ensure every network is finalised
for (var network : maximals) {
@@ -260,7 +259,7 @@ final class WiredNetworkImpl implements WiredNetwork {
var change = WiredNetworkChangeImpl.changeOf(oldPeripherals, newPeripherals);
if (change.isEmpty()) return;
wired.peripherals = ImmutableMap.copyOf(newPeripherals);
wired.peripherals = Map.copyOf(newPeripherals);
// Detach the old peripherals then remove them.
peripherals.keySet().removeAll(change.peripheralsRemoved().keySet());
@@ -333,7 +332,7 @@ final class WiredNetworkImpl implements WiredNetwork {
// Detach the old peripherals then remove them from the old network
wired.network = wiredNetwork;
wired.neighbours.clear();
wired.peripherals = Collections.emptyMap();
wired.peripherals = Map.of();
// Broadcast the change
if (!peripherals.isEmpty()) WiredNetworkChangeImpl.removed(peripherals).broadcast(wired);

View File

@@ -13,13 +13,16 @@ import dan200.computercraft.api.network.wired.WiredSender;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nullable;
import java.util.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public final class WiredNodeImpl implements WiredNode {
private @Nullable Set<PacketReceiver> receivers;
final WiredElement element;
Map<String, IPeripheral> peripherals = Collections.emptyMap();
Map<String, IPeripheral> peripherals = Map.of();
final HashSet<WiredNodeImpl> neighbours = new HashSet<>();
volatile WiredNetworkImpl network;

View File

@@ -89,7 +89,7 @@ public final class CommandComputerCraft {
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
.suggests((context, builder) -> Suggestions.empty())
)
.argManyValue("args", StringArgumentType.string(), Collections.emptyList())
.argManyValue("args", StringArgumentType.string(), List.of())
.executes((c, a) -> queue(getComputersArgument(c, "computer"), a)))
.then(command("view")
@@ -300,7 +300,7 @@ public final class CommandComputerCraft {
return 1;
}
private static final List<AggregatedMetric> DEFAULT_FIELDS = Arrays.asList(
private static final List<AggregatedMetric> DEFAULT_FIELDS = List.of(
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.COUNT),
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.NONE),
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG)

View File

@@ -32,7 +32,7 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
private static final ComputersArgumentType MANY = new ComputersArgumentType(false);
private static final ComputersArgumentType SOME = new ComputersArgumentType(true);
private static final List<String> EXAMPLES = Arrays.asList(
private static final List<String> EXAMPLES = List.of(
"0", "#0", "@Label", "~Advanced"
);
@@ -75,7 +75,7 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
var instance = reader.readInt();
computers = s -> {
var computer = ServerContext.get(s.getServer()).registry().get(instance);
return computer == null ? Collections.emptyList() : Collections.singletonList(computer);
return computer == null ? List.of() : List.of(computer);
};
}

View File

@@ -15,7 +15,6 @@ import net.minecraft.commands.CommandSourceStack;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -63,7 +62,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
}
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, T defaultValue) {
return argManyValue(name, type, Collections.singletonList(defaultValue));
return argManyValue(name, type, List.of(defaultValue));
}
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argMany(String name, ArgumentType<T> type, Supplier<List<T>> empty) {

View File

@@ -134,12 +134,12 @@ public class CommandAPI implements ILuaAPI {
public final List<String> list(IArguments args) throws LuaException {
var server = computer.getLevel().getServer();
if (server == null) return Collections.emptyList();
if (server == null) return List.of();
CommandNode<CommandSourceStack> node = server.getCommands().getDispatcher().getRoot();
for (var j = 0; j < args.count(); j++) {
var name = args.getString(j);
node = node.getChild(name);
if (!(node instanceof LiteralCommandNode)) return Collections.emptyList();
if (!(node instanceof LiteralCommandNode)) return List.of();
}
List<String> result = new ArrayList<>();

View File

@@ -21,6 +21,8 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static dan200.computercraft.core.filesystem.MountHelpers.NO_SUCH_FILE;
/**
* A mount backed by Minecraft's {@link ResourceManager}.
*

View File

@@ -30,7 +30,6 @@ public final class ConfigSpec {
public static final ConfigFile.Value<Integer> computerSpaceLimit;
public static final ConfigFile.Value<Integer> floppySpaceLimit;
public static final ConfigFile.Value<Integer> maximumFilesOpen;
public static final ConfigFile.Value<Boolean> disableLua51Features;
public static final ConfigFile.Value<String> defaultComputerSettings;
public static final ConfigFile.Value<Boolean> logComputerErrors;
public static final ConfigFile.Value<Boolean> commandRequireCreative;
@@ -115,12 +114,6 @@ public final class ConfigSpec {
.comment("Set how many files a computer can have open at the same time. Set to 0 for unlimited.")
.defineInRange("maximum_open_files", CoreConfig.maximumFilesOpen, 0, Integer.MAX_VALUE);
disableLua51Features = builder
.comment("""
Set this to true to disable Lua 5.1 functions that will be removed in a future
update. Useful for ensuring forward compatibility of your programs now.""")
.define("disable_lua51_features", CoreConfig.disableLua51Features);
defaultComputerSettings = builder
.comment("""
A comma separated list of default system settings to set on new computers.
@@ -395,7 +388,6 @@ public final class ConfigSpec {
Config.floppySpaceLimit = floppySpaceLimit.get();
Config.uploadMaxSize = uploadMaxSize.get();
CoreConfig.maximumFilesOpen = maximumFilesOpen.get();
CoreConfig.disableLua51Features = disableLua51Features.get();
CoreConfig.defaultComputerSettings = defaultComputerSettings.get();
Config.commandRequireCreative = commandRequireCreative.get();

View File

@@ -12,7 +12,6 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import java.util.Collections;
import java.util.Set;
/**
@@ -33,7 +32,7 @@ public final class BlockNamedEntityLootCondition implements LootItemCondition {
@Override
public Set<LootContextParam<?>> getReferencedContextParams() {
return Collections.singleton(LootContextParams.BLOCK_ENTITY);
return Set.of(LootContextParams.BLOCK_ENTITY);
}
@Override

View File

@@ -12,7 +12,6 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import java.util.Collections;
import java.util.Set;
/**
@@ -33,7 +32,7 @@ public final class HasComputerIdLootCondition implements LootItemCondition {
@Override
public Set<LootContextParam<?>> getReferencedContextParams() {
return Collections.singleton(LootContextParams.BLOCK_ENTITY);
return Set.of(LootContextParams.BLOCK_ENTITY);
}
@Override

View File

@@ -12,7 +12,6 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import java.util.Collections;
import java.util.Set;
/**
@@ -33,7 +32,7 @@ public final class PlayerCreativeLootCondition implements LootItemCondition {
@Override
public Set<LootContextParam<?>> getReferencedContextParams() {
return Collections.singleton(LootContextParams.THIS_ENTITY);
return Set.of(LootContextParams.THIS_ENTITY);
}
@Override

View File

@@ -9,14 +9,12 @@ import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
@@ -24,7 +22,6 @@ import java.util.function.Supplier;
* Utilities for recipe mod plugins (such as JEI).
*/
public final class RecipeModHelpers {
static final List<ComputerFamily> MAIN_FAMILIES = Arrays.asList(ComputerFamily.NORMAL, ComputerFamily.ADVANCED);
static final List<Supplier<TurtleItem>> TURTLES = List.of(ModRegistry.Items.TURTLE_NORMAL, ModRegistry.Items.TURTLE_ADVANCED);
static final List<Supplier<PocketComputerItem>> POCKET_COMPUTERS = List.of(ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED);

View File

@@ -114,7 +114,7 @@ public class UpgradeRecipeGenerator<T> {
// Suggest possible upgrades which can be applied to this turtle
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
if (left != null && right != null) return Collections.emptyList();
if (left != null && right != null) return List.of();
List<T> recipes = new ArrayList<>();
var ingredient = Ingredient.of(stack);
@@ -135,7 +135,7 @@ public class UpgradeRecipeGenerator<T> {
} else if (stack.getItem() instanceof PocketComputerItem) {
// Suggest possible upgrades which can be applied to this turtle
var back = PocketComputerItem.getUpgrade(stack);
if (back != null) return Collections.emptyList();
if (back != null) return List.of();
List<T> recipes = new ArrayList<>();
var ingredient = Ingredient.of(stack);
@@ -148,7 +148,7 @@ public class UpgradeRecipeGenerator<T> {
} else {
// If this item is usable as an upgrade, find all possible recipes.
var upgrades = upgradeItemLookup.get(stack.getItem());
if (upgrades == null) return Collections.emptyList();
if (upgrades == null) return List.of();
List<T> recipes = null;
var multiple = false;
@@ -169,7 +169,7 @@ public class UpgradeRecipeGenerator<T> {
}
}
return recipes == null ? Collections.emptyList() : Collections.unmodifiableList(recipes);
return recipes == null ? List.of() : Collections.unmodifiableList(recipes);
}
}
@@ -215,7 +215,7 @@ public class UpgradeRecipeGenerator<T> {
return Collections.unmodifiableList(recipes);
} else {
return Collections.emptyList();
return List.of();
}
}

View File

@@ -22,7 +22,7 @@ import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.Collections;
import java.util.List;
@JeiPlugin
public class JEIComputerCraft implements IModPlugin {
@@ -61,7 +61,7 @@ public class JEIComputerCraft implements IModPlugin {
var category = registry.createRecipeLookup(RecipeTypes.CRAFTING);
category.get().forEach(wrapper -> {
if (RecipeModHelpers.shouldRemoveRecipe(wrapper.getId())) {
registry.hideRecipes(RecipeTypes.CRAFTING, Collections.singleton(wrapper));
registry.hideRecipes(RecipeTypes.CRAFTING, List.of(wrapper));
}
});
}

View File

@@ -15,7 +15,6 @@ import mezz.jei.api.recipe.category.IRecipeCategory;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingRecipe;
import java.util.Collections;
import java.util.List;
class RecipeResolver implements IRecipeManagerPlugin {
@@ -24,36 +23,36 @@ class RecipeResolver implements IRecipeManagerPlugin {
@Override
public <V> List<RecipeType<?>> getRecipeTypes(IFocus<V> focus) {
var value = focus.getTypedValue().getIngredient();
if (!(value instanceof ItemStack stack)) return Collections.emptyList();
if (!(value instanceof ItemStack stack)) return List.of();
return switch (focus.getRole()) {
case INPUT ->
stack.getItem() instanceof TurtleItem || stack.getItem() instanceof PocketComputerItem || resolver.isUpgrade(stack)
? Collections.singletonList(RecipeTypes.CRAFTING)
: Collections.emptyList();
? List.of(RecipeTypes.CRAFTING)
: List.of();
case OUTPUT -> stack.getItem() instanceof TurtleItem || stack.getItem() instanceof PocketComputerItem
? Collections.singletonList(RecipeTypes.CRAFTING)
: Collections.emptyList();
default -> Collections.emptyList();
? List.of(RecipeTypes.CRAFTING)
: List.of();
default -> List.of();
};
}
@Override
public <T, V> List<T> getRecipes(IRecipeCategory<T> recipeCategory, IFocus<V> focus) {
if (!(focus.getTypedValue().getIngredient() instanceof ItemStack stack) || recipeCategory.getRecipeType() != RecipeTypes.CRAFTING) {
return Collections.emptyList();
return List.of();
}
return switch (focus.getRole()) {
case INPUT -> cast(resolver.findRecipesWithInput(stack));
case OUTPUT -> cast(resolver.findRecipesWithOutput(stack));
default -> Collections.emptyList();
default -> List.of();
};
}
@Override
public <T> List<T> getRecipes(IRecipeCategory<T> recipeCategory) {
return Collections.emptyList();
return List.of();
}
@SuppressWarnings({ "unchecked", "rawtypes" })

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.peripheral.generic;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
/**
* Extract some component (for instance a capability on Forge, or a {@code BlockApiLookup} on Fabric) from a block and
* block entity.
*
* @param <C> A platform-specific type, used for the invalidation callback.
*/
public interface ComponentLookup<C extends Runnable> {
/**
* Extract some component from a block in the world.
*
* @param level The current level.
* @param pos The position of the block in the level.
* @param state The block state at that position.
* @param blockEntity The block entity at that position.
* @param side The side of the block to extract the component from. Implementations should try to use a
* sideless lookup first, but may fall back to a sided lookup if needed.
* @param invalidate An invalidation function to call if this component changes.
* @return The found component, or {@code null} if not present.
*/
@Nullable
Object find(Level level, BlockPos pos, BlockState state, BlockEntity blockEntity, Direction side, C invalidate);
}

View File

@@ -6,12 +6,9 @@ package dan200.computercraft.shared.peripheral.generic;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.core.methods.MethodSupplier;
import dan200.computercraft.core.methods.NamedMethod;
import dan200.computercraft.core.methods.PeripheralMethod;
import dan200.computercraft.shared.computer.core.ServerContext;
import net.minecraft.core.Direction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.block.entity.BlockEntity;
import javax.annotation.Nullable;
@@ -28,16 +25,10 @@ import java.util.Set;
* See the platform-specific peripheral providers for the usage of this.
*/
final class GenericPeripheralBuilder {
private final MethodSupplier<PeripheralMethod> peripheralMethods;
private @Nullable String name;
private final Set<String> additionalTypes = new HashSet<>(0);
private final ArrayList<SaturatedMethod> methods = new ArrayList<>();
GenericPeripheralBuilder(MinecraftServer server) {
peripheralMethods = ServerContext.get(server).peripheralMethods();
}
@Nullable
IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) {
if (methods.isEmpty()) return null;
@@ -46,18 +37,16 @@ final class GenericPeripheralBuilder {
return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods);
}
boolean addMethods(Object target) {
return peripheralMethods.forEachSelfMethod(target, (name, method, info) -> {
methods.add(new SaturatedMethod(target, name, method));
void addMethod(Object target, String name, PeripheralMethod method, @Nullable NamedMethod<PeripheralMethod> info) {
methods.add(new SaturatedMethod(target, name, method));
// If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods
// don't change).
var type = info == null ? null : info.genericType();
if (type != null && type.getPrimaryType() != null) {
var primaryType = type.getPrimaryType();
if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType;
}
if (type != null) additionalTypes.addAll(type.getAdditionalTypes());
});
// If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods
// don't change).
var type = info == null ? null : info.genericType();
if (type != null && type.getPrimaryType() != null) {
var primaryType = type.getPrimaryType();
if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType;
}
if (type != null) additionalTypes.addAll(type.getAdditionalTypes());
}
}

View File

@@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.peripheral.generic;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.methods.MethodSupplier;
import dan200.computercraft.core.methods.PeripheralMethod;
import dan200.computercraft.shared.computer.core.ServerContext;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A peripheral provider which finds methods from various {@linkplain GenericSource generic sources}.
* <p>
* Methods are found using the original block entity itself and a registered list of {@link ComponentLookup}s.
*
* @param <C> A platform-specific type, used for the invalidation callback.
*/
public final class GenericPeripheralProvider<C extends Runnable> {
private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class);
private final List<ComponentLookup<? super C>> lookups = new ArrayList<>();
/**
* Register a component lookup function.
*
* @param lookup The component lookup function.
*/
public synchronized void registerLookup(ComponentLookup<? super C> lookup) {
Objects.requireNonNull(lookup);
if (!lookups.contains(lookup)) lookups.add(lookup);
}
public void forEachMethod(MethodSupplier<PeripheralMethod> methods, Level level, BlockPos pos, Direction side, BlockEntity blockEntity, C invalidate, MethodSupplier.TargetedConsumer<PeripheralMethod> consumer) {
methods.forEachMethod(blockEntity, consumer);
for (var lookup : lookups) {
var contents = lookup.find(level, pos, blockEntity.getBlockState(), blockEntity, side, invalidate);
if (contents != null) methods.forEachMethod(contents, consumer);
}
}
@Nullable
public IPeripheral getPeripheral(Level level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity, C invalidate) {
if (blockEntity == null) return null;
var server = level.getServer();
if (server == null) {
LOG.warn("Fetching peripherals on a non-server level {}.", level, new IllegalStateException("Fetching peripherals on a non-server level."));
return null;
}
var builder = new GenericPeripheralBuilder();
forEachMethod(ServerContext.get(server).peripheralMethods(), level, pos, side, blockEntity, invalidate, builder::addMethod);
return builder.toPeripheral(blockEntity, side);
}
}

View File

@@ -4,12 +4,12 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.WaterloggableHelpers;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
@@ -52,12 +52,14 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
public static final BooleanProperty UP = BooleanProperty.create("up");
public static final BooleanProperty DOWN = BooleanProperty.create("down");
static final EnumMap<Direction, BooleanProperty> CONNECTIONS =
new EnumMap<>(new ImmutableMap.Builder<Direction, BooleanProperty>()
.put(Direction.DOWN, DOWN).put(Direction.UP, UP)
.put(Direction.NORTH, NORTH).put(Direction.SOUTH, SOUTH)
.put(Direction.WEST, WEST).put(Direction.EAST, EAST)
.build());
static final EnumMap<Direction, BooleanProperty> CONNECTIONS = Util.make(new EnumMap<>(Direction.class), m -> {
m.put(Direction.DOWN, DOWN);
m.put(Direction.UP, UP);
m.put(Direction.NORTH, NORTH);
m.put(Direction.SOUTH, SOUTH);
m.put(Direction.WEST, WEST);
m.put(Direction.EAST, EAST);
});
public CableBlock(Properties settings) {
super(settings);

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.base.Objects;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
@@ -31,7 +30,8 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
public class CableBlockEntity extends BlockEntity {
private static final String NBT_PERIPHERAL_ENABLED = "PeripheralAccess";
@@ -181,7 +181,7 @@ public class CableBlockEntity extends BlockEntity {
var oldName = peripheral.getConnectedName();
togglePeripheralAccess();
var newName = peripheral.getConnectedName();
if (!Objects.equal(newName, oldName)) {
if (!Objects.equals(newName, oldName)) {
if (oldName != null) {
player.displayClientMessage(Component.translatable("chat.computercraft.wired_modem.peripheral_disconnected",
ChatHelpers.copy(oldName)), false);
@@ -274,7 +274,7 @@ public class CableBlockEntity extends BlockEntity {
if (!canAttachPeripheral() && peripheralAccessAllowed) {
peripheralAccessAllowed = false;
peripheral.detach();
node.updatePeripherals(Collections.emptyMap());
node.updatePeripherals(Map.of());
setChanged();
updateBlockState();
}
@@ -291,7 +291,7 @@ public class CableBlockEntity extends BlockEntity {
peripheral.detach();
peripheralAccessAllowed = false;
node.updatePeripherals(Collections.emptyMap());
node.updatePeripherals(Map.of());
}
updateBlockState();

View File

@@ -4,9 +4,9 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.shared.peripheral.modem.ModemShapes;
import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.Util;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.Shapes;
@@ -21,16 +21,14 @@ public final class CableShapes {
private static final double MAX = 1 - MIN;
private static final VoxelShape SHAPE_CABLE_CORE = Shapes.box(MIN, MIN, MIN, MAX, MAX, MAX);
private static final EnumMap<Direction, VoxelShape> SHAPE_CABLE_ARM =
new EnumMap<>(new ImmutableMap.Builder<Direction, VoxelShape>()
.put(Direction.DOWN, Shapes.box(MIN, 0, MIN, MAX, MIN, MAX))
.put(Direction.UP, Shapes.box(MIN, MAX, MIN, MAX, 1, MAX))
.put(Direction.NORTH, Shapes.box(MIN, MIN, 0, MAX, MAX, MIN))
.put(Direction.SOUTH, Shapes.box(MIN, MIN, MAX, MAX, MAX, 1))
.put(Direction.WEST, Shapes.box(0, MIN, MIN, MIN, MAX, MAX))
.put(Direction.EAST, Shapes.box(MAX, MIN, MIN, 1, MAX, MAX))
.build()
);
private static final EnumMap<Direction, VoxelShape> SHAPE_CABLE_ARM = Util.make(new EnumMap<>(Direction.class), m -> {
m.put(Direction.DOWN, Shapes.box(MIN, 0, MIN, MAX, MIN, MAX));
m.put(Direction.UP, Shapes.box(MIN, MAX, MIN, MAX, 1, MAX));
m.put(Direction.NORTH, Shapes.box(MIN, MIN, 0, MAX, MAX, MIN));
m.put(Direction.SOUTH, Shapes.box(MIN, MIN, MAX, MAX, MAX, 1));
m.put(Direction.WEST, Shapes.box(0, MIN, MIN, MIN, MAX, MAX));
m.put(Direction.EAST, Shapes.box(MAX, MIN, MIN, 1, MAX, MAX));
});
private static final VoxelShape[] SHAPES = new VoxelShape[(1 << 6) * 7];
private static final VoxelShape[] CABLE_SHAPES = new VoxelShape[1 << 6];

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.base.Objects;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
@@ -133,7 +132,7 @@ public class WiredModemFullBlockEntity extends BlockEntity {
togglePeripheralAccess();
var periphNames = getConnectedPeripheralNames();
if (!Objects.equal(periphNames, oldPeriphNames)) {
if (!Objects.equals(periphNames, oldPeriphNames)) {
sendPeripheralChanges(player, "chat.computercraft.wired_modem.peripheral_disconnected", oldPeriphNames);
sendPeripheralChanges(player, "chat.computercraft.wired_modem.peripheral_connected", periphNames);
}
@@ -241,14 +240,14 @@ public class WiredModemFullBlockEntity extends BlockEntity {
peripheralAccessAllowed = false;
for (var peripheral : peripherals) peripheral.detach();
node.updatePeripherals(Collections.emptyMap());
node.updatePeripherals(Map.of());
}
updateBlockState();
}
private Set<String> getConnectedPeripheralNames() {
if (!peripheralAccessAllowed) return Collections.emptySet();
if (!peripheralAccessAllowed) return Set.of();
Set<String> peripherals = new HashSet<>(6);
for (var peripheral : this.peripherals) {
@@ -259,7 +258,7 @@ public class WiredModemFullBlockEntity extends BlockEntity {
}
private Map<String, IPeripheral> getConnectedPeripherals() {
if (!peripheralAccessAllowed) return Collections.emptyMap();
if (!peripheralAccessAllowed) return Map.of();
Map<String, IPeripheral> peripherals = new HashMap<>(6);
for (var peripheral : this.peripherals) peripheral.extendMap(peripherals);

View File

@@ -103,7 +103,7 @@ public final class WiredModemLocalPeripheral {
public Map<String, IPeripheral> toMap() {
return peripheral == null
? Collections.emptyMap()
? Map.of()
: Collections.singletonMap(type + "_" + id, peripheral);
}

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.*;
@@ -69,7 +68,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
@Override
public Set<String> getAdditionalTypes() {
return Collections.singleton("peripheral_hub");
return Set.of("peripheral_hub");
}
//region Peripheral methods
@@ -89,7 +88,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
@LuaFunction
public final Collection<String> getNamesRemote(IComputerAccess computer) {
var wrappers = getWrappers(computer);
return wrappers == null ? Collections.emptySet() : wrappers.keySet();
return wrappers == null ? Set.of() : wrappers.keySet();
}
/**
@@ -429,7 +428,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
public Map<String, IPeripheral> getAvailablePeripherals() {
if (!attached) throw new NotAttachedException();
synchronized (element.getRemotePeripherals()) {
return ImmutableMap.copyOf(element.getRemotePeripherals());
return Map.copyOf(element.getRemotePeripherals());
}
}

View File

@@ -157,7 +157,6 @@ public class MonitorBlockEntity extends BlockEntity {
if (xIndex == 0 && yIndex == 0) {
// If we're the origin, set up the new monitor
serverMonitor = new ServerMonitor(advanced, this);
serverMonitor.rebuild();
// And propagate it to child monitors
for (var x = 0; x < width; x++) {
@@ -178,6 +177,11 @@ public class MonitorBlockEntity extends BlockEntity {
}
}
private void createServerTerminal() {
var monitor = createServerMonitor();
if (monitor != null && monitor.getTerminal() == null) monitor.rebuild();
}
@Nullable
public ClientMonitor getClientMonitor() {
if (clientMonitor != null) return clientMonitor;
@@ -377,6 +381,8 @@ public class MonitorBlockEntity extends BlockEntity {
BlockEntityHelpers.updateBlock(monitor);
}
}
assertInvariant();
}
void updateNeighborsDeferred() {
@@ -487,9 +493,10 @@ public class MonitorBlockEntity extends BlockEntity {
}
public IPeripheral peripheral() {
createServerMonitor();
if (peripheral != null) return peripheral;
return peripheral = new MonitorPeripheral(this);
createServerTerminal();
var peripheral = this.peripheral != null ? this.peripheral : (this.peripheral = new MonitorPeripheral(this));
assertInvariant();
return peripheral;
}
void addComputer(IComputerAccess computer) {
@@ -528,4 +535,85 @@ public class MonitorBlockEntity extends BlockEntity {
Math.max(startPos.getZ(), endPos.getZ()) + 1
);
}
/**
* Assert all {@linkplain #checkInvariants() monitor invariants} hold.
*/
private void assertInvariant() {
assert checkInvariants() : "Monitor invariants failed. See logs.";
}
/**
* Check various invariants about this monitor multiblock. This is only called when assertions are enabled, so
* will be skipped outside of tests.
*
* @return Whether all invariants passed.
*/
private boolean checkInvariants() {
LOG.debug("Checking monitor invariants at {}", getBlockPos());
var okay = true;
if (width <= 0 || height <= 0) {
okay = false;
LOG.error("Monitor {} has non-positive of {}x{}", getBlockPos(), width, height);
}
var hasPeripheral = false;
var origin = getOrigin().getMonitor();
var serverMonitor = origin != null ? origin.serverMonitor : this.serverMonitor;
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
var monitor = getLoadedMonitor(x, y).getMonitor();
if (monitor == null) continue;
hasPeripheral |= monitor.peripheral != null;
if (monitor.serverMonitor != null && monitor.serverMonitor != serverMonitor) {
okay = false;
LOG.error(
"Monitor {} expected to be have serverMonitor={}, but was {}",
monitor.getBlockPos(), serverMonitor, monitor.serverMonitor
);
}
if (monitor.xIndex != x || monitor.yIndex != y) {
okay = false;
LOG.error(
"Monitor {} expected to be at {},{}, but believes it is {},{}",
monitor.getBlockPos(), x, y, monitor.xIndex, monitor.yIndex
);
}
if (monitor.width != width || monitor.height != height) {
okay = false;
LOG.error(
"Monitor {} expected to be size {},{}, but believes it is {},{}",
monitor.getBlockPos(), width, height, monitor.width, monitor.height
);
}
var expectedState = getBlockState().setValue(MonitorBlock.STATE, MonitorEdgeState.fromConnections(
y < height - 1, y > 0, x > 0, x < width - 1
));
if (monitor.getBlockState() != expectedState) {
okay = false;
LOG.error(
"Monitor {} expected to have state {}, but has state {}",
monitor.getBlockState(), expectedState, monitor.getBlockState()
);
}
}
}
if (hasPeripheral != (serverMonitor != null && serverMonitor.getTerminal() != null)) {
okay = false;
LOG.error(
"Peripheral is {}, but serverMonitor={} and serverMonitor.terminal={}",
hasPeripheral, serverMonitor, serverMonitor == null ? null : serverMonitor.getTerminal()
);
}
return okay;
}
}

View File

@@ -107,7 +107,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
@Override
@Deprecated(forRemoval = true)
public Map<ResourceLocation, IPeripheral> getUpgrades() {
return upgrade == null ? Collections.emptyMap() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
return upgrade == null ? Map.of() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
}
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.shared.pocket.items;
import com.google.common.base.Objects;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.Mount;
@@ -44,6 +43,7 @@ import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;
public class PocketComputerItem extends Item implements IComputerItem, IMedia, IColouredItem {
private static final String NBT_UPGRADE = "Upgrade";
@@ -97,7 +97,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
// Sync label
var label = computer.getLabel();
if (!Objects.equal(label, getLabel(stack))) {
if (!Objects.equals(label, getLabel(stack))) {
changed = true;
setLabel(stack, label);
}

View File

@@ -21,7 +21,10 @@ public final class RecipeUtil {
}
public static NonNullList<Ingredient> readIngredients(FriendlyByteBuf buffer) {
return buffer.readCollection(x -> NonNullList.withSize(x, Ingredient.EMPTY), Ingredient::fromNetwork);
var count = buffer.readVarInt();
var ingredients = NonNullList.withSize(count, Ingredient.EMPTY);
for (var i = 0; i < ingredients.size(); i++) ingredients.set(i, Ingredient.fromNetwork(buffer));
return ingredients;
}
public static void writeIngredients(FriendlyByteBuf buffer, NonNullList<Ingredient> ingredients) {

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.shared.turtle.core;
import com.google.common.base.Objects;
import com.mojang.authlib.GameProfile;
import dan200.computercraft.api.lua.ILuaCallback;
import dan200.computercraft.api.lua.MethodResult;
@@ -455,7 +454,7 @@ public class TurtleBrain implements TurtleAccessInternal {
}
public void setOverlay(@Nullable ResourceLocation overlay) {
if (!Objects.equal(this.overlay, overlay)) {
if (!Objects.equals(this.overlay, overlay)) {
this.overlay = overlay;
BlockEntityHelpers.updateBlock(owner);
}
@@ -573,7 +572,7 @@ public class TurtleBrain implements TurtleAccessInternal {
public float getToolRenderAngle(TurtleSide side, float f) {
return (side == TurtleSide.LEFT && animation == TurtleAnimation.SWING_LEFT_TOOL) ||
(side == TurtleSide.RIGHT && animation == TurtleAnimation.SWING_RIGHT_TOOL)
(side == TurtleSide.RIGHT && animation == TurtleAnimation.SWING_RIGHT_TOOL)
? 45.0f * (float) Math.sin(getAnimationFraction(f) * Math.PI)
: 0.0f;
}

View File

@@ -27,7 +27,6 @@ import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.entity.SignText;
import net.minecraft.world.level.block.state.BlockState;
@@ -186,7 +185,7 @@ public class TurtlePlaceCommand implements TurtleCommand {
tile = world.getBlockEntity(position.relative(side));
}
if (tile instanceof SignBlockEntity) setSignText(world, tile, message);
if (tile instanceof SignBlockEntity sign) setSignText(world, sign, message);
}
return placed;
@@ -220,23 +219,20 @@ public class TurtlePlaceCommand implements TurtleCommand {
return InteractionResult.PASS;
}
private static void setSignText(Level world, BlockEntity tile, String message) {
var signTile = (SignBlockEntity) tile;
var split = Splitter.on('\n').splitToList(message);
var firstLine = split.size() <= 2 ? 1 : 0;
private static void setSignText(Level world, SignBlockEntity sign, String message) {
var lines = Splitter.on('\n').splitToList(message);
var firstLine = lines.size() <= 2 ? 1 : 0;
var signText = new SignText();
for (var i = 0; i < 4; i++) {
if (i >= firstLine && i < firstLine + split.size()) {
var line = split.get(i - firstLine);
signText.setMessage(i, line.length() > 15
? Component.literal(line.substring(0, 15))
: Component.literal(line)
);
}
for (int i = 0, len = Math.min(lines.size(), 4); i < len; i++) {
var line = lines.get(i);
signText = signText.setMessage(i + firstLine, line.length() > 15
? Component.literal(line.substring(0, 15))
: Component.literal(line)
);
}
signTile.setText(signText, true);
world.sendBlockUpdated(tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), Block.UPDATE_ALL);
sign.setText(signText, true);
world.sendBlockUpdated(sign.getBlockPos(), sign.getBlockState(), sign.getBlockState(), Block.UPDATE_ALL);
}
private static final class ErrorMessage {

View File

@@ -70,7 +70,7 @@ public class TurtleInventoryCrafting implements CraftingContainer {
if (recipe == null) return null;
// Special case: craft(0) just returns an empty list if crafting was possible
if (maxCount == 0) return Collections.emptyList();
if (maxCount == 0) return List.of();
var player = TurtlePlayer.get(turtle).player();

View File

@@ -20,7 +20,6 @@ import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -38,7 +37,7 @@ public final class NBTUtil {
try {
var ctor = CompoundTag.class.getDeclaredConstructor(Map.class);
ctor.setAccessible(true);
EMPTY_TAG = ctor.newInstance(Collections.emptyMap());
EMPTY_TAG = ctor.newInstance(Map.of());
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}

View File

@@ -74,8 +74,6 @@
"gui.computercraft.config.computer_space_limit.tooltip": "Limit diskového místa pro počítače a roboty, v bytech.",
"gui.computercraft.config.default_computer_settings": "Vychozí nastavení počítače",
"gui.computercraft.config.default_computer_settings.tooltip": "Čárkami oddělený seznam základních systémových nastavení pro nastavení na nových počítačích.\nPříklad: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nvypne všechno automatické vyplňování.",
"gui.computercraft.config.disable_lua51_features": "Vypnout Lua 5.1 funkce",
"gui.computercraft.config.disable_lua51_features.tooltip": "Nastav toto na pravdivou hodnotu pro vypnutí Lua 5.1 funkcí které budou odstraněny v budoucí\naktualizaci. Dobré pro ověření kompatibilty tvých programů nyní a předem.",
"gui.computercraft.config.disabled_generic_methods": "Vypnuté obecné metody",
"gui.computercraft.config.execution": "Spuštení",
"gui.computercraft.config.execution.computer_threads": "Počítačová vlákna",

View File

@@ -27,6 +27,7 @@
"commands.computercraft.desc": "Der /computercraft Befehl enthält verschiedene Werkzeuge um Computer zu debuggen, kontrollieren oder mit ihnen zu interagieren.",
"commands.computercraft.dump.action": "Zeigt mehr Informationen über einen Computer",
"commands.computercraft.dump.desc": "Zeigt den Status aller Computer oder genauere Informationen über einen angegebenen Computer. Der Computer kann entweder über seine Instanz ID (z.B. 123), seine Computer ID (z.B. #123) oder seinen Namen (z.B. \"@Mein Computer\") angegeben werden.",
"commands.computercraft.dump.open_path": "Siehe die Dateien dieses Computers.",
"commands.computercraft.dump.synopsis": "Zeigt den Status eines Computers.",
"commands.computercraft.generic.additional_rows": "%d zusätzliche Zeilen…",
"commands.computercraft.generic.exception": "Unbehandelte Ausnahme (%s)",
@@ -68,12 +69,11 @@
"commands.computercraft.view.not_player": "Konnte Terminal für Nicht-Spieler nicht öffnen",
"commands.computercraft.view.synopsis": "Zeigt das Terminal eines Computers.",
"gui.computercraft.config.command_require_creative": "Kommando-Computer benötigen den Kreativ-Modus",
"gui.computercraft.config.command_require_creative.tooltip": "Benötigt Spieler sowohl im Kreativ-Spielmodus, als auch ein Operator zu sein,\num mit Kommando-Computern zu interagieren. Dies entspricht dem standardmäßigen Verhalten von Befehlsblöcken.",
"gui.computercraft.config.computer_space_limit": "Speicherplatz von Computern (Bytes)",
"gui.computercraft.config.computer_space_limit.tooltip": "Das Speicherplatzlimit für Computer und Turtles in Bytes.",
"gui.computercraft.config.default_computer_settings": "Computer-Standardeinstellungen",
"gui.computercraft.config.default_computer_settings.tooltip": "eine mit Komma separierte Liste an standardmäßige Systemeinstellungen für neuen Computern.\nBeispiel: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nwürde jegliche Autovervollständigung deaktivieren.",
"gui.computercraft.config.disable_lua51_features": "Lua 5.1-Funktionen deaktivieren",
"gui.computercraft.config.disable_lua51_features.tooltip": "Beim aktivieren werden die Lua 5.1 Funktionieren deaktiviert, die in zukünftigen\nUpdates sowieso entfernt werden. Auf dieser Weise kann vorwärts Kompatibilität für Programme sichergestellt werden.",
"gui.computercraft.config.disabled_generic_methods": "Generische Methoden deaktiviert.",
"gui.computercraft.config.disabled_generic_methods.tooltip": "Eine Liste an generischen Methoden oder Methodenquellen zum deaktivieren.\nGenerische Methoden sind Methoden die zu einem block/block entity hinzugefügt werden, insofern kein expliziter Peripheral Provider\ngefunden wurde. Mitbetroffen sind Inventarmethoden (d.h. inventory.getItemDetail,\ninventory.pushItems) und, wenn in Forge gespielt wird, die fluid_storage und energy_storage\nMethoden.\nMethoden in dieser Liste können entweder Gruppen von Methoden (wie computercraft:inventory)\noder einzelne Methoden (wie computercraft:inventory#pushItems) sein.\n",
"gui.computercraft.config.execution": "Ausführung",

View File

@@ -33,7 +33,6 @@
"commands.computercraft.shutdown.synopsis": "Apague las computadoras de forma remota.",
"gui.computercraft.config.computer_space_limit": "Límite de memoria de ordenadores (en bytes)",
"gui.computercraft.config.default_computer_settings": "Configuración de Ordenador por defecto",
"gui.computercraft.config.disable_lua51_features": "Deshabilitar funciones de Lua 5.1",
"gui.computercraft.config.floppy_space_limit": "Límite de memoria de disquetes (en bytes)",
"gui.computercraft.config.http.enabled": "Habilitar API de HTTP",
"gui.computercraft.config.log_computer_errors": "Grabar errores periféricos",

View File

@@ -74,9 +74,8 @@
"gui.computercraft.config.computer_space_limit.tooltip": "La limite d'espace du disque pour les ordinateurs et les tortues, en octets.",
"gui.computercraft.config.default_computer_settings": "Configuration d'Ordinateur par défaut",
"gui.computercraft.config.default_computer_settings.tooltip": "Une liste séparée par des virgules des paramètres système par défaut à définir sur les nouveaux ordinateurs.\nExemple : \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\ndésactivera toute saisie semi-automatique.",
"gui.computercraft.config.disable_lua51_features": "Désactiver les particularités de Lua 5.1",
"gui.computercraft.config.disable_lua51_features.tooltip": "Définir sur true pour désactiver les fonctions Lua 5.1 qui seront supprimées dans une future mise à jour.\nUtile pour assurer la compatibilité ascendante de vos programmes actuels.",
"gui.computercraft.config.disabled_generic_methods": "Méthodes génériques désactivées",
"gui.computercraft.config.disabled_generic_methods.tooltip": "Une liste de méthodes génériques ou de méthodes source à désactiver.\nLes méthodes génériques sont ajoutées à un bloc ou bloc-entité lorsque aucune implémentation explicite de périphérique n'est disponible. Cela inclut les méthodes liées à l'inventaire (ex. inventory.getItemDetail, inventory.pushItems) et, avec Forge, les méthodes fluid_storage et energy_storage.\nLes méthodes de cette liste peuvent être groupées (computercraft:inventory) ou individuelles (computercraft:inventory#pushItems).\n",
"gui.computercraft.config.execution": "Exécution",
"gui.computercraft.config.execution.computer_threads": "Threads d'ordinateur",
"gui.computercraft.config.execution.computer_threads.tooltip": "Définissez le nombre de threads sur lesquels les ordinateurs peuvent s'exécuter. Un nombre plus élevé signifie\nque plus d'ordinateurs peuvent fonctionner à la fois, mais peut induire un décalage. Veuillez noter que\ncertains mods peuvent ne pas fonctionner avec un nombre de threads supérieur à 1. À utiliser avec prudence.\nPlage : > 1",
@@ -95,7 +94,7 @@
"gui.computercraft.config.http.bandwidth.global_upload.tooltip": "Le nombre d'octets qui peuvent être téléversé en une seconde. Ceci est partagé sur tous les ordinateurs. (octets/s).\nPlage : > 1",
"gui.computercraft.config.http.bandwidth.tooltip": "Limite la bande passante utilisée par les ordinateurs.",
"gui.computercraft.config.http.enabled": "Permettre l'API HTTP",
"gui.computercraft.config.http.enabled.tooltip": "Active l'API \"http\" sur les ordinateurs. Cela désactive également les programmes \"pastebin\" et \"wget\",\nsur lesquels de nombreux utilisateurs comptent. Il est recommandé de laisser cette option activée et\nd'utiliser l'option de configuration \"rules\" pour imposer un contrôle plus précis.",
"gui.computercraft.config.http.enabled.tooltip": "Active l'API \"http\" sur les ordinateurs. La désactiver empêche également l'utilisation des programmes \"pastebin\" et \"wget\",\nsur lesquels de nombreux utilisateurs comptent. Il est recommandé de laisser cette option activée et\nd'utiliser l'option de configuration \"rules\" pour imposer un contrôle plus précis.",
"gui.computercraft.config.http.max_requests": "Maximum de requêtes simultanées",
"gui.computercraft.config.http.max_requests.tooltip": "Le nombre de requêtes http qu'un ordinateur peut effectuer en même temps.\nLes demandes supplémentaires seront mises en file d'attente et envoyées lorsque\nles demandes en cours seront terminées. Mettre à 0 pour illimité.\nPlage : > 0",
"gui.computercraft.config.http.max_websockets": "Maximum de websockets en simultané",
@@ -200,6 +199,8 @@
"item.computercraft.printed_pages": "Pages imprimées",
"item.computercraft.treasure_disk": "Disquette",
"itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "Ordinateurs",
"tag.item.computercraft.wired_modem": "Modems câblés",
"tracking_field.computercraft.avg": "%s (moyenne)",
"tracking_field.computercraft.computer_tasks.name": "Tâches",
"tracking_field.computercraft.count": "%s (compte)",

View File

@@ -74,8 +74,6 @@
"gui.computercraft.config.computer_space_limit.tooltip": "Limite di spazio di archiviazione per i computer e le tartarughe, in byte.",
"gui.computercraft.config.default_computer_settings": "Impostazioni Computer predefinite",
"gui.computercraft.config.default_computer_settings.tooltip": "Una lista di impostazioni predefinite per i nuovi computer, separate da virgola.\nEsempio: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\ndisattiverà tutti gli autocompletamenti.",
"gui.computercraft.config.disable_lua51_features": "Disattiva features Lua 5.1",
"gui.computercraft.config.disable_lua51_features.tooltip": "Imposta a \"true\" per disattivare le funzionalità di Lua 5.1 che saranno rimosse in un\naggiornamento futuro. Utile per assicurare futura compatibilità con i tuoi programmi.",
"gui.computercraft.config.execution": "Esecuzione",
"gui.computercraft.config.execution.computer_threads": "Threads computer",
"gui.computercraft.config.execution.computer_threads.tooltip": "Imposta la quantità di thread che possono eseguire i computer. Un numero più alto significa\nche più computer possono essere eseguiti alla volta, ma può indurre a lag. Alcune mod potrebbero\nnon funzionare con numeri di thread maggiore a 1. Usare con cautela.\nRange: > 1",

View File

@@ -70,7 +70,6 @@
"commands.computercraft.view.synopsis": "컴퓨터의 터미널을 보기",
"gui.computercraft.config.computer_space_limit": "컴퓨터 공간 제한 (바이트)",
"gui.computercraft.config.default_computer_settings": "기본 컴퓨터 설정",
"gui.computercraft.config.disable_lua51_features": "Lua 5.1 기능 미사용",
"gui.computercraft.config.execution": "실행",
"gui.computercraft.config.execution.computer_threads": "컴퓨터 쓰레드",
"gui.computercraft.config.execution.max_main_computer_time": "컴퓨터 시간 당 서버 제한",

View File

@@ -38,7 +38,6 @@
"commands.computercraft.turn_on.synopsis": "Liga computadores remotamente.",
"gui.computercraft.config.computer_space_limit": "Limite de espaço dos Computadores (bytes)",
"gui.computercraft.config.default_computer_settings": "Configurações padrão para Computadores",
"gui.computercraft.config.disable_lua51_features": "Desabilitar funcionalidade da Lua 5.1",
"gui.computercraft.config.execution.computer_threads": "Threads por computador",
"gui.computercraft.config.floppy_space_limit": "Limite de espaço dos Disquetes (bytes)",
"gui.computercraft.config.http": "HTTP",

View File

@@ -74,8 +74,6 @@
"gui.computercraft.config.computer_space_limit.tooltip": "Лимит места на дисках компьютеров и черепашек, в байтах.",
"gui.computercraft.config.default_computer_settings": "Настройки Компьютера по умолчанию",
"gui.computercraft.config.default_computer_settings.tooltip": "Разделенный запятыми список системных настроек по умолчанию на новых компьютерах.\nНапример: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nотключит всё автодополнение.",
"gui.computercraft.config.disable_lua51_features": "Отключить функции Lua 5.1",
"gui.computercraft.config.disable_lua51_features.tooltip": "Поставьте, чтобы отключить функции из Lua 5.1, которые будут убраны в будущих\nобновлениях. Полезно для того, чтобы улучшить совместимость вперед ваших программ.",
"gui.computercraft.config.execution": "Выполнение",
"gui.computercraft.config.execution.computer_threads": "Потоки компьютера",
"gui.computercraft.config.execution.computer_threads.tooltip": "Устанавливает количество потоков, на которых работают компьютеры. Большее число\nозначает, что больше компьютеров сможет работать одновременно, но может привести к лагу.\nОбратите внимание, что некоторые моды могут не работать с более чем одним потоком. Используйте с осторожностью.\nОграничение: > 1",

View File

@@ -70,7 +70,6 @@
"commands.computercraft.view.synopsis": "Titta på datorns terminal.",
"gui.computercraft.config.computer_space_limit": "Dator maximalt utrymme (bytes)",
"gui.computercraft.config.default_computer_settings": "Standard Datorinställningar",
"gui.computercraft.config.disable_lua51_features": "Avaktivera Lua 5.1 funktioner",
"gui.computercraft.config.execution.computer_threads": "Dator trådar",
"gui.computercraft.config.floppy_space_limit": "Diskett maximalt utrymme (bytes)",
"gui.computercraft.config.http": "HTTP",

View File

@@ -66,7 +66,6 @@
"commands.computercraft.view.not_player": "jan ala la, mi ken ala open e sitelen pi ilo sona",
"commands.computercraft.view.synopsis": "o lukin e sitelen pi ilo sona.",
"gui.computercraft.config.command_require_creative": "ilo sona pi toki wawa li wile e jan sewi",
"gui.computercraft.config.disable_lua51_features": "ijo pona pi ilo Lua 5.1 li weka",
"gui.computercraft.config.execution": "pali",
"gui.computercraft.config.execution.computer_threads": "linja pi ilo sona",
"gui.computercraft.config.http": "ijo HTTP",

View File

@@ -74,8 +74,6 @@
"gui.computercraft.config.computer_space_limit.tooltip": "Обмеження на займаєме місце на диску комп'ютерами та черепахами, в байтах.",
"gui.computercraft.config.default_computer_settings": "Налаштування комп'ютера за замовчуванням",
"gui.computercraft.config.default_computer_settings.tooltip": "Список лашатувань за замовчуванням, розділених комою, що будуть нашалтовані на нових комп'ютерах\nНаприклад: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nвідключить усі автодоповнення.",
"gui.computercraft.config.disable_lua51_features": "Відключити підтримку Lua 5.1",
"gui.computercraft.config.disable_lua51_features.tooltip": "Включить це налаштування, якщо хочете відключити функціональність, пов'язану з Lua 5.1, яка буде вилучен в наступних оновленнях.\nТаким чином ви можете впевнитись, що ваші програми не зламаються від подальших оновлень.",
"gui.computercraft.config.execution": "Виконання",
"gui.computercraft.config.execution.computer_threads": "Потоки для комп'ютерів",
"gui.computercraft.config.execution.computer_threads.tooltip": "Встановлює кількість потоків, на яких запускаються комп'ютери. Більше число\nозначає більшу кількість комп'ютерів, які працюють паралельно, але може призвести до проблем із продуктивністю.\nЗауважте, що деякі модифікації можуть не працювати із кількістю потоків більше за 1. Використовуйте з обережністю.\nОбмеження: > 1",

View File

@@ -68,7 +68,6 @@
"commands.computercraft.view.synopsis": "查看计算机的终端.",
"gui.computercraft.config.computer_space_limit": "计算机空间限制(字节)",
"gui.computercraft.config.default_computer_settings": "默认计算机设置",
"gui.computercraft.config.disable_lua51_features": "禁用Lua 5.1功能",
"gui.computercraft.config.execution": "执行",
"gui.computercraft.config.execution.computer_threads": "计算机线程数",
"gui.computercraft.config.execution.max_main_computer_time": "服务器计算机tick时间限制",

View File

@@ -4,8 +4,6 @@
package dan200.computercraft.impl.network.wired;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNetwork;
import dan200.computercraft.api.network.wired.WiredNetworkChange;
@@ -19,6 +17,7 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -47,24 +46,24 @@ public class NetworkTest {
assertFalse(aN.getNetwork().connect(aN, bN), "Cannot add connection twice");
assertEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal");
assertEquals(Sets.newHashSet(aN, bN), nodes(aN.getNetwork()), "A's network should be A and B");
assertEquals(Set.of(aN, bN), nodes(aN.getNetwork()), "A's network should be A and B");
assertEquals(Sets.newHashSet("a", "b"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B");
assertEquals(Sets.newHashSet("a", "b"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B");
assertEquals(Set.of("a", "b"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B");
assertEquals(Set.of("a", "b"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B");
aN.getNetwork().connect(aN, cN);
assertEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal");
assertEquals(aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal");
assertEquals(Sets.newHashSet(aN, bN, cN), nodes(aN.getNetwork()), "A's network should be A, B and C");
assertEquals(Set.of(aN, bN, cN), nodes(aN.getNetwork()), "A's network should be A, B and C");
assertEquals(Sets.newHashSet(bN, cN), neighbours(aN), "A's neighbour set should be B, C");
assertEquals(Sets.newHashSet(aN), neighbours(bN), "B's neighbour set should be A");
assertEquals(Sets.newHashSet(aN), neighbours(cN), "C's neighbour set should be A");
assertEquals(Set.of(bN, cN), neighbours(aN), "A's neighbour set should be B, C");
assertEquals(Set.of(aN), neighbours(bN), "B's neighbour set should be A");
assertEquals(Set.of(aN), neighbours(cN), "C's neighbour set should be A");
assertEquals(Sets.newHashSet("a", "b", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C");
assertEquals(Sets.newHashSet("a", "b", "c"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C");
assertEquals(Sets.newHashSet("a", "b", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C");
assertEquals(Set.of("a", "b", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C");
assertEquals(Set.of("a", "b", "c"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C");
assertEquals(Set.of("a", "b", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C");
}
@Test
@@ -87,11 +86,11 @@ public class NetworkTest {
assertEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal");
assertEquals(aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal");
assertEquals(Sets.newHashSet(aN, bN, cN), nodes(aN.getNetwork()), "A's network should be A, B and C");
assertEquals(Set.of(aN, bN, cN), nodes(aN.getNetwork()), "A's network should be A, B and C");
assertEquals(Sets.newHashSet("a", "b", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C");
assertEquals(Sets.newHashSet("a", "b", "c"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C");
assertEquals(Sets.newHashSet("a", "b", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C");
assertEquals(Set.of("a", "b", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C");
assertEquals(Set.of("a", "b", "c"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C");
assertEquals(Set.of("a", "b", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C");
}
@Test
@@ -113,12 +112,12 @@ public class NetworkTest {
assertNotEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal");
assertEquals(aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal");
assertEquals(Sets.newHashSet(aN, cN), nodes(aN.getNetwork()), "A's network should be A and C");
assertEquals(Sets.newHashSet(bN), nodes(bN.getNetwork()), "B's network should be B");
assertEquals(Set.of(aN, cN), nodes(aN.getNetwork()), "A's network should be A and C");
assertEquals(Set.of(bN), nodes(bN.getNetwork()), "B's network should be B");
assertEquals(Sets.newHashSet("a", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, C");
assertEquals(Sets.newHashSet("b"), bE.allPeripherals().keySet(), "B's peripheral set should be B");
assertEquals(Sets.newHashSet("a", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, C");
assertEquals(Set.of("a", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, C");
assertEquals(Set.of("b"), bE.allPeripherals().keySet(), "B's peripheral set should be B");
assertEquals(Set.of("a", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, C");
}
@Test
@@ -146,11 +145,11 @@ public class NetworkTest {
assertEquals(aN.getNetwork(), aaN.getNetwork(), "A's and A_'s network must be equal");
assertEquals(bN.getNetwork(), bbN.getNetwork(), "B's and B_'s network must be equal");
assertEquals(Sets.newHashSet(aN, aaN), nodes(aN.getNetwork()), "A's network should be A and A_");
assertEquals(Sets.newHashSet(bN, bbN), nodes(bN.getNetwork()), "B's network should be B and B_");
assertEquals(Set.of(aN, aaN), nodes(aN.getNetwork()), "A's network should be A and A_");
assertEquals(Set.of(bN, bbN), nodes(bN.getNetwork()), "B's network should be B and B_");
assertEquals(Sets.newHashSet("a", "a_"), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_");
assertEquals(Sets.newHashSet("b", "b_"), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_");
assertEquals(Set.of("a", "a_"), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_");
assertEquals(Set.of("b", "b_"), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_");
}
@Test
@@ -184,12 +183,12 @@ public class NetworkTest {
assertNotEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal");
assertEquals(aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal");
assertEquals(Sets.newHashSet(aN, cN), nodes(aN.getNetwork()), "A's network should be A and C");
assertEquals(Sets.newHashSet(bN), nodes(bN.getNetwork()), "B's network should be B");
assertEquals(Set.of(aN, cN), nodes(aN.getNetwork()), "A's network should be A and C");
assertEquals(Set.of(bN), nodes(bN.getNetwork()), "B's network should be B");
assertEquals(Sets.newHashSet("a", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, C");
assertEquals(Sets.newHashSet(), bE.allPeripherals().keySet(), "B's peripheral set should be empty");
assertEquals(Sets.newHashSet("a", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, C");
assertEquals(Set.of("a", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, C");
assertEquals(Set.of(), bE.allPeripherals().keySet(), "B's peripheral set should be empty");
assertEquals(Set.of("a", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, C");
}
@Test
@@ -220,13 +219,13 @@ public class NetworkTest {
assertEquals(aN.getNetwork(), aaN.getNetwork(), "A's and A_'s network must be equal");
assertEquals(bN.getNetwork(), bbN.getNetwork(), "B's and B_'s network must be equal");
assertEquals(Sets.newHashSet(aN, aaN), nodes(aN.getNetwork()), "A's network should be A and A_");
assertEquals(Sets.newHashSet(bN, bbN), nodes(bN.getNetwork()), "B's network should be B and B_");
assertEquals(Sets.newHashSet(cN), nodes(cN.getNetwork()), "C's network should be C");
assertEquals(Set.of(aN, aaN), nodes(aN.getNetwork()), "A's network should be A and A_");
assertEquals(Set.of(bN, bbN), nodes(bN.getNetwork()), "B's network should be B and B_");
assertEquals(Set.of(cN), nodes(cN.getNetwork()), "C's network should be C");
assertEquals(Sets.newHashSet("a", "a_"), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_");
assertEquals(Sets.newHashSet("b", "b_"), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_");
assertEquals(Sets.newHashSet(), cE.allPeripherals().keySet(), "C's peripheral set should be empty");
assertEquals(Set.of("a", "a_"), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_");
assertEquals(Set.of("b", "b_"), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_");
assertEquals(Set.of(), cE.allPeripherals().keySet(), "C's peripheral set should be empty");
}
private static final int BRUTE_SIZE = 16;
@@ -300,8 +299,8 @@ public class NetworkTest {
private final Vec3 position;
private final String id;
private final WiredNode node;
private final Map<String, IPeripheral> localPeripherals = Maps.newHashMap();
private final Map<String, IPeripheral> remotePeripherals = Maps.newHashMap();
private final Map<String, IPeripheral> localPeripherals = new HashMap<>();
private final Map<String, IPeripheral> remotePeripherals = new HashMap<>();
private NetworkElement(Level world, Vec3 position, String id) {
this.world = world;

View File

@@ -16,7 +16,6 @@ import org.hamcrest.Matcher;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -110,7 +109,7 @@ public class UploadFileMessageTest {
@Provide
Arbitrary<FileUpload> fileUpload() {
return Combinators.combine(
Arbitraries.oneOf(Arrays.asList(
Arbitraries.oneOf(List.of(
// 1.16 doesn't correctly handle unicode file names. We'll be generous in our tests here.
Arbitraries.strings().ofMinLength(1).ascii().ofMaxLength(MAX_FILE_NAME),
Arbitraries.strings().ofMinLength(1).ofMaxLength(MAX_FILE_NAME / 4)

View File

@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.recipe;
import dan200.computercraft.test.shared.MinecraftArbitraries;
import it.unimi.dsi.fastutil.ints.IntIntImmutablePair;
import net.jqwik.api.Arbitraries;
import net.jqwik.api.Arbitrary;
import net.jqwik.api.Combinators;
import net.minecraft.core.NonNullList;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.Ingredient;
/**
* {@link Arbitrary} implementations for recipes.
*/
public final class RecipeArbitraries {
public static Arbitrary<RecipeProperties> recipeProperties() {
return Combinators.combine(
Arbitraries.strings().ofMinLength(1).withChars("abcdefghijklmnopqrstuvwxyz_"),
Arbitraries.of(CraftingBookCategory.values())
).as(RecipeProperties::new);
}
public static Arbitrary<ShapelessRecipeSpec> shapelessRecipeSpec() {
return Combinators.combine(
recipeProperties(),
MinecraftArbitraries.ingredient().array(Ingredient[].class).ofMinSize(1).map(x -> NonNullList.of(Ingredient.EMPTY, x)),
MinecraftArbitraries.nonEmptyItemStack()
).as(ShapelessRecipeSpec::new);
}
public static Arbitrary<ShapedTemplate> shapedTemplate() {
return Combinators.combine(Arbitraries.integers().between(1, 3), Arbitraries.integers().between(1, 3))
.as(IntIntImmutablePair::new)
.flatMap(x -> MinecraftArbitraries.ingredient().array(Ingredient[].class).ofSize(x.leftInt() * x.rightInt())
.map(i -> new ShapedTemplate(x.leftInt(), x.rightInt(), NonNullList.of(Ingredient.EMPTY, i)))
);
}
public static Arbitrary<ShapedRecipeSpec> shapedRecipeSpec() {
return Combinators.combine(
recipeProperties(),
shapedTemplate(),
MinecraftArbitraries.nonEmptyItemStack()
).as(ShapedRecipeSpec::new);
}
}

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.recipe;
import dan200.computercraft.test.core.StructuralEquality;
import dan200.computercraft.test.shared.MinecraftEqualities;
/**
* {@link StructuralEquality} implementations for recipes.
*/
public final class RecipeEqualities {
private RecipeEqualities() {
}
public static final StructuralEquality<ShapelessRecipeSpec> shapelessRecipeSpec = StructuralEquality.all(
StructuralEquality.at("properties", ShapelessRecipeSpec::properties),
StructuralEquality.at("ingredients", ShapelessRecipeSpec::ingredients, MinecraftEqualities.ingredient.list()),
StructuralEquality.at("result", ShapelessRecipeSpec::result, MinecraftEqualities.itemStack)
);
public static final StructuralEquality<ShapedTemplate> shapedTemplate = StructuralEquality.all(
StructuralEquality.at("width", ShapedTemplate::width),
StructuralEquality.at("height", ShapedTemplate::height),
StructuralEquality.at("ingredients", ShapedTemplate::ingredients, MinecraftEqualities.ingredient.list())
);
public static final StructuralEquality<ShapedRecipeSpec> shapedRecipeSpec = StructuralEquality.all(
StructuralEquality.at("properties", ShapedRecipeSpec::properties),
StructuralEquality.at("ingredients", ShapedRecipeSpec::template, shapedTemplate),
StructuralEquality.at("result", ShapedRecipeSpec::result, MinecraftEqualities.itemStack)
);
}

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.recipe;
import dan200.computercraft.test.shared.NetworkSupport;
import dan200.computercraft.test.shared.WithMinecraft;
import net.jqwik.api.Arbitrary;
import net.jqwik.api.ForAll;
import net.jqwik.api.Property;
import net.jqwik.api.Provide;
import static org.hamcrest.MatcherAssert.assertThat;
@WithMinecraft
public class ShapedRecipeSpecTest {
static {
WithMinecraft.Setup.bootstrap(); // @Property doesn't run test lifecycle methods.
}
@Property
public void testRoundTrip(@ForAll("recipe") ShapedRecipeSpec spec) {
var converted = NetworkSupport.roundTrip(spec, ShapedRecipeSpec::toNetwork, ShapedRecipeSpec::fromNetwork);
assertThat("Recipes are equal", converted, RecipeEqualities.shapedRecipeSpec.asMatcher(ShapedRecipeSpec.class, spec));
}
@Provide
Arbitrary<ShapedRecipeSpec> recipe() {
return RecipeArbitraries.shapedRecipeSpec();
}
}

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.recipe;
import dan200.computercraft.test.shared.NetworkSupport;
import dan200.computercraft.test.shared.WithMinecraft;
import net.jqwik.api.Arbitrary;
import net.jqwik.api.ForAll;
import net.jqwik.api.Property;
import net.jqwik.api.Provide;
import static org.hamcrest.MatcherAssert.assertThat;
@WithMinecraft
public class ShapelessRecipeSpecTest {
static {
WithMinecraft.Setup.bootstrap(); // @Property doesn't run test lifecycle methods.
}
@Property
public void testRoundTrip(@ForAll("recipe") ShapelessRecipeSpec spec) {
var converted = NetworkSupport.roundTrip(spec, ShapelessRecipeSpec::toNetwork, ShapelessRecipeSpec::fromNetwork);
assertThat("Recipes are equal", converted, RecipeEqualities.shapelessRecipeSpec.asMatcher(ShapelessRecipeSpec.class, spec));
}
@Provide
Arbitrary<ShapelessRecipeSpec> recipe() {
return RecipeArbitraries.shapelessRecipeSpec();
}
}

View File

@@ -8,16 +8,13 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleToolDurability;
import dan200.computercraft.test.core.StructuralEquality;
import dan200.computercraft.test.shared.MinecraftArbitraries;
import dan200.computercraft.test.shared.MinecraftEqualities;
import dan200.computercraft.test.shared.NetworkSupport;
import dan200.computercraft.test.shared.WithMinecraft;
import io.netty.buffer.Unpooled;
import net.jqwik.api.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.item.ItemStack;
import org.hamcrest.Description;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
@WithMinecraft
class TurtleToolSerialiserTest {
@@ -32,11 +29,9 @@ class TurtleToolSerialiserTest {
*/
@Property
public void testRoundTrip(@ForAll("tool") TurtleTool tool) {
var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
TurtleToolSerialiser.INSTANCE.toNetwork(buffer, tool);
var converted = TurtleToolSerialiser.INSTANCE.fromNetwork(tool.getUpgradeID(), buffer);
assertEquals(buffer.readableBytes(), 0, "Whole packet was read");
var converted = NetworkSupport.roundTripSerialiser(
tool.getUpgradeID(), tool, TurtleToolSerialiser.INSTANCE::toNetwork, TurtleToolSerialiser.INSTANCE::fromNetwork
);
if (!equality.equals(tool, converted)) {
System.out.println("Break");
@@ -58,22 +53,10 @@ class TurtleToolSerialiserTest {
).as(TurtleTool::new);
}
private static final StructuralEquality<ItemStack> stackEquality = new StructuralEquality<>() {
@Override
public boolean equals(ItemStack left, ItemStack right) {
return ItemStack.isSameItemSameTags(left, right) && left.getCount() == right.getCount();
}
@Override
public void describe(Description description, ItemStack object) {
description.appendValue(object).appendValue(object.getTag());
}
};
private static final StructuralEquality<TurtleTool> equality = StructuralEquality.all(
StructuralEquality.at("id", ITurtleUpgrade::getUpgradeID),
StructuralEquality.at("craftingItem", ITurtleUpgrade::getCraftingItem, stackEquality),
StructuralEquality.at("tool", x -> x.item, stackEquality),
StructuralEquality.at("craftingItem", ITurtleUpgrade::getCraftingItem, MinecraftEqualities.itemStack),
StructuralEquality.at("tool", x -> x.item, MinecraftEqualities.itemStack),
StructuralEquality.at("damageMulitiplier", x -> x.damageMulitiplier),
StructuralEquality.at("allowEnchantments", x -> x.allowEnchantments),
StructuralEquality.at("consumeDurability", x -> x.consumeDurability),

View File

@@ -17,6 +17,7 @@ import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import java.util.List;
@@ -44,6 +45,10 @@ public final class MinecraftArbitraries {
return Arbitraries.oneOf(List.of(Arbitraries.just(ItemStack.EMPTY), nonEmptyItemStack()));
}
public static Arbitrary<Ingredient> ingredient() {
return nonEmptyItemStack().list().ofMinSize(1).map(x -> Ingredient.of(x.stream()));
}
public static Arbitrary<BlockPos> blockPos() {
// BlockPos has a maximum range that can be sent over the network - use those.
var xz = Arbitraries.integers().between(-3_000_000, -3_000_000);

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.test.shared;
import dan200.computercraft.test.core.StructuralEquality;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.hamcrest.Description;
/**
* {@link StructuralEquality} implementations for Minecraft types.
*/
public class MinecraftEqualities {
public static final StructuralEquality<ItemStack> itemStack = new StructuralEquality<>() {
@Override
public boolean equals(ItemStack left, ItemStack right) {
return ItemStack.isSameItemSameTags(left, right) && left.getCount() == right.getCount();
}
@Override
public void describe(Description description, ItemStack object) {
description.appendValue(object).appendValue(object.getTag());
}
};
public static final StructuralEquality<Ingredient> ingredient = new StructuralEquality<>() {
@Override
public boolean equals(Ingredient left, Ingredient right) {
return left.toJson().equals(right.toJson());
}
@Override
public void describe(Description description, Ingredient object) {
description.appendValue(object.toJson());
}
};
}

View File

@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.test.shared;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeSerializer;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Support methods for working with Minecraft's networking code.
*/
public final class NetworkSupport {
private NetworkSupport() {
}
/**
* Attempt to serialise and then deserialise a value.
*
* @param value The value to serialise.
* @param write Serialise this value to a buffer.
* @param read Deserialise this value from a buffer.
* @param <T> The type of the value to round trip.
* @return The converted value, for checking equivalency.
*/
public static <T> T roundTrip(T value, BiConsumer<T, FriendlyByteBuf> write, Function<FriendlyByteBuf, T> read) {
var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
write.accept(value, buffer);
var converted = read.apply(buffer);
assertEquals(buffer.readableBytes(), 0, "Whole packet was read");
return converted;
}
/**
* Attempt to serialise and then deserialise a value from a {@link RecipeSerializer}-like interface.
*
* @param id The id of this value.
* @param value The value to serialise.
* @param write Serialise this value to a buffer.
* @param read Deserialise this value from a buffer.
* @param <T> The type of the value to round trip.
* @return The converted value, for checking equivalency.
*/
public static <T> T roundTripSerialiser(ResourceLocation id, T value, BiConsumer<FriendlyByteBuf, T> write, BiFunction<ResourceLocation, FriendlyByteBuf, T> read) {
return roundTrip(value, (x, b) -> write.accept(b, x), b -> read.apply(id, b));
}
}

View File

@@ -52,8 +52,8 @@ public class JsonDump {
inputs[pos] = itemIds;
}
private static final Set<Item> canonicalItem = new HashSet<>(Arrays.asList(
private static final Set<Item> canonicalItem = Set.of(
Items.GLASS_PANE, Items.STONE, Items.CHEST
));
);
}
}

View File

@@ -17,7 +17,10 @@ import net.minecraft.gametest.framework.GameTestGenerator
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.gametest.framework.TestFunction
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.entity.EntityType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.Blocks
import org.junit.jupiter.api.Assertions.*
import java.util.*
class Monitor_Test {
@@ -32,7 +35,7 @@ class Monitor_Test {
val toSet = BlockInput(
ModRegistry.Blocks.MONITOR_ADVANCED.get().defaultBlockState(),
Collections.emptySet(),
emptySet(),
tag,
)
@@ -61,6 +64,45 @@ class Monitor_Test {
}
}
/**
* When a monitor is destroyed and then replaced, the terminal is recreated.
*/
@GameTest
fun Creates_terminal(helper: GameTestHelper) = helper.sequence {
fun monitorAt(x: Int) =
helper.getBlockEntity(BlockPos(x, 2, 2), ModRegistry.BlockEntities.MONITOR_ADVANCED.get())
thenExecute {
for (i in 1..3) {
assertNull(monitorAt(i).cachedServerMonitor, "Monitor $i starts with no ServerMonitor")
}
monitorAt(2).peripheral()
assertNotNull(monitorAt(1).cachedServerMonitor?.terminal, "Creating a peripheral creates a terminal")
// Then remove the middle monitor and check it splits into two.
helper.setBlock(BlockPos(2, 2, 2), Blocks.AIR.defaultBlockState())
assertNotNull(monitorAt(3).cachedServerMonitor, "Origin retains its monitor")
assertNull(monitorAt(3).cachedServerMonitor!!.terminal, "Origin deletes the terminal")
assertNotEquals(monitorAt(1).cachedServerMonitor, monitorAt(3).cachedServerMonitor, "Monitors are different")
// Then set the monitor, check it rejoins and recreates the terminal.
val pos = BlockPos(2, 2, 2)
helper.setBlock(pos, ModRegistry.Blocks.MONITOR_ADVANCED.get())
ModRegistry.Blocks.MONITOR_ADVANCED.get().setPlacedBy(
helper.level,
helper.absolutePos(pos),
helper.getBlockState(pos),
EntityType.COW.create(helper.level),
ItemStack.EMPTY,
)
monitorAt(2).peripheral()
assertNotNull(monitorAt(1).cachedServerMonitor?.terminal, "Recreates the terminal")
}
}
/**
* Test monitors render correctly
*/

View File

@@ -37,6 +37,7 @@ import net.minecraft.world.item.Items
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FenceBlock
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.array
@@ -86,6 +87,26 @@ class Turtle_Test {
thenExecute { helper.assertBlockPresent(Blocks.LAVA, BlockPos(2, 2, 2)) }
}
/**
* Checks turtles can write to signs.
*
* @see [#1611](https://github.com/cc-tweaked/CC-Tweaked/issues/1611)
*/
@GameTest
fun Place_sign(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
turtle.place(ObjectArguments("Test\nmessage")).await()
.assertArrayEquals(true, message = "Placed sign")
}
thenExecute {
val sign = helper.getBlockEntity(BlockPos(2, 2, 1), BlockEntityType.SIGN)
val lines = listOf("", "Test", "message", "")
for ((i, line) in lines.withIndex()) {
assertEquals(line, sign.frontText.getMessage(i, false).string, "Line $i")
}
}
}
/**
* Checks that calling [net.minecraft.world.item.Item.use] will not place blocks too far away.
*

View File

@@ -17,7 +17,7 @@ import net.minecraft.gametest.framework.GameTestAssertException
import net.minecraft.gametest.framework.GameTestAssertPosException
import net.minecraft.gametest.framework.GameTestInfo
import net.minecraft.gametest.framework.GameTestSequence
import org.apache.logging.log4j.LogManager
import org.slf4j.LoggerFactory
import java.io.InputStream
import java.util.*
import java.util.concurrent.CancellationException
@@ -33,7 +33,7 @@ import java.util.concurrent.atomic.AtomicReference
* @see GameTestSequence.thenOnComputer
*/
object ManagedComputers : ILuaMachine.Factory {
private val LOGGER = LogManager.getLogger(ManagedComputers::class.java)
private val LOGGER = LoggerFactory.getLogger(ManagedComputers::class.java)
private val computers: MutableMap<String, Queue<suspend LuaTaskContext.() -> Unit>> = mutableMapOf()
internal fun enqueue(test: GameTestInfo, label: String, task: suspend LuaTaskContext.() -> Unit): Monitor {

View File

@@ -0,0 +1,139 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:l}", nbt: {Height: 1, Width: 3, XIndex: 2, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lr}", nbt: {Height: 1, Width: 3, XIndex: 1, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:r}", nbt: {Height: 1, Width: 3, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:monitor_advanced{facing:north,orientation:north,state:l}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:lr}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:r}"
]
}

View File

@@ -0,0 +1,137 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:oak_sign"}], Label: "turtle_test.place_sign", On: 1b, Owner: {LowerId: -7553144124013011039L, Name: "Player840", UpperId: 8473952144820417152L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:north,waterlogged:false}"
]
}

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.lua.LuaFunction;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Set;
/**
@@ -35,7 +34,7 @@ public interface IPeripheral {
* @see PeripheralType#getAdditionalTypes()
*/
default Set<String> getAdditionalTypes() {
return Collections.emptySet();
return Set.of();
}
/**

View File

@@ -6,7 +6,6 @@ package dan200.computercraft.api.peripheral;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
/**
@@ -16,7 +15,7 @@ import java.util.Set;
* lexicographically smallest non-empty name being chosen.
*/
public final class PeripheralType {
private static final PeripheralType UNTYPED = new PeripheralType(null, Collections.emptySet());
private static final PeripheralType UNTYPED = new PeripheralType(null, Set.of());
private final @Nullable String type;
private final Set<String> additionalTypes;
@@ -46,7 +45,7 @@ public final class PeripheralType {
*/
public static PeripheralType ofType(String type) {
checkTypeName("type cannot be null or empty");
return new PeripheralType(type, Collections.emptySet());
return new PeripheralType(type, Set.of());
}
/**
@@ -118,8 +117,8 @@ public final class PeripheralType {
}
private static Set<String> getTypes(Collection<String> types) {
if (types.isEmpty()) return Collections.emptySet();
if (types.size() == 1) return Collections.singleton(types.iterator().next());
if (types.isEmpty()) return Set.of();
if (types.size() == 1) return Set.of(types.iterator().next());
return Set.copyOf(types);
}
}

View File

@@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.getAbsolutePath
plugins {
`java-library`
`java-test-fixtures`
@@ -24,13 +26,13 @@ dependencies {
implementation(libs.netty.socks)
implementation(libs.netty.proxy)
implementation(libs.slf4j)
implementation(libs.asm)
testFixturesImplementation(libs.slf4j)
testFixturesApi(platform(libs.kotlin.platform))
testFixturesApi(libs.bundles.test)
testFixturesApi(libs.bundles.kotlin)
testImplementation(libs.asm)
testImplementation(libs.bundles.test)
testRuntimeOnly(libs.bundles.testRuntime)
testRuntimeOnly(libs.slf4j.simple)
@@ -45,7 +47,7 @@ tasks.processResources {
}
tasks.test {
systemProperty("cct.test-files", buildDir.resolve("tmp/testFiles").absolutePath)
systemProperty("cct.test-files", layout.buildDirectory.dir("tmp/testFiles").getAbsolutePath())
}
tasks.testFixturesJar {

View File

@@ -8,8 +8,9 @@ import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.core.asm.GenericMethod;
import dan200.computercraft.core.asm.LuaMethodSupplier;
import dan200.computercraft.core.asm.PeripheralMethodSupplier;
import dan200.computercraft.core.computer.ComputerThread;
import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.computer.computerthread.ComputerScheduler;
import dan200.computercraft.core.computer.computerthread.ComputerThread;
import dan200.computercraft.core.computer.mainthread.MainThreadScheduler;
import dan200.computercraft.core.computer.mainthread.NoWorkMainThreadScheduler;
import dan200.computercraft.core.lua.CobaltLuaMachine;
@@ -31,7 +32,7 @@ import java.util.concurrent.TimeUnit;
*/
public final class ComputerContext {
private final GlobalEnvironment globalEnvironment;
private final ComputerThread computerScheduler;
private final ComputerScheduler computerScheduler;
private final MainThreadScheduler mainThreadScheduler;
private final ILuaMachine.Factory luaFactory;
private final List<ILuaAPIFactory> apiFactories;
@@ -39,7 +40,7 @@ public final class ComputerContext {
private final MethodSupplier<PeripheralMethod> peripheralMethods;
ComputerContext(
GlobalEnvironment globalEnvironment, ComputerThread computerScheduler,
GlobalEnvironment globalEnvironment, ComputerScheduler computerScheduler,
MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory,
List<ILuaAPIFactory> apiFactories, MethodSupplier<LuaMethod> luaMethods,
MethodSupplier<PeripheralMethod> peripheralMethods
@@ -68,7 +69,7 @@ public final class ComputerContext {
*
* @return The current computer thread manager.
*/
public ComputerThread computerScheduler() {
public ComputerScheduler computerScheduler() {
return computerScheduler;
}
@@ -162,7 +163,7 @@ public final class ComputerContext {
*/
public static class Builder {
private final GlobalEnvironment environment;
private int threads = 1;
private @Nullable ComputerScheduler computerScheduler = null;
private @Nullable MainThreadScheduler mainThreadScheduler;
private @Nullable ILuaMachine.Factory luaFactory;
private @Nullable List<ILuaAPIFactory> apiFactories;
@@ -173,7 +174,7 @@ public final class ComputerContext {
}
/**
* Set the number of threads the {@link ComputerThread} will use.
* Set the {@link #computerScheduler()} to use {@link ComputerThread} with a given number of threads.
*
* @param threads The number of threads to use.
* @return {@code this}, for chaining
@@ -181,7 +182,20 @@ public final class ComputerContext {
*/
public Builder computerThreads(int threads) {
if (threads < 1) throw new IllegalArgumentException("Threads must be >= 1");
this.threads = threads;
return computerScheduler(new ComputerThread(threads));
}
/**
* Set the {@link ComputerScheduler} for this context.
*
* @param scheduler The computer thread scheduler.
* @return {@code this}, for chaining
* @see ComputerContext#mainThreadScheduler()
*/
public Builder computerScheduler(ComputerScheduler scheduler) {
Objects.requireNonNull(scheduler);
if (computerScheduler != null) throw new IllegalStateException("Computer scheduler already specified");
computerScheduler = scheduler;
return this;
}
@@ -250,7 +264,7 @@ public final class ComputerContext {
public ComputerContext build() {
return new ComputerContext(
environment,
new ComputerThread(threads),
computerScheduler == null ? new ComputerThread(1) : computerScheduler,
mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler,
luaFactory == null ? CobaltLuaMachine::new : luaFactory,
apiFactories == null ? List.of() : apiFactories,

View File

@@ -22,7 +22,6 @@ public final class CoreConfig {
}
public static int maximumFilesOpen = 128;
public static boolean disableLua51Features = false;
public static String defaultComputerSettings = "";
public static boolean httpEnabled = true;

View File

@@ -18,7 +18,6 @@ import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
@@ -83,7 +82,7 @@ public class HTTPAPI implements ILuaAPI {
var options = args.getTable(0);
address = getStringField(options, "url");
postString = optStringField(options, "body", null);
headerTable = optTableField(options, "headers", Collections.emptyMap());
headerTable = optTableField(options, "headers", Map.of());
binary = optBooleanField(options, "binary", false);
requestMethod = optStringField(options, "method", null);
redirect = optBooleanField(options, "redirect", true);
@@ -92,7 +91,7 @@ public class HTTPAPI implements ILuaAPI {
// Get URL and post information
address = args.getString(0);
postString = args.optString(1, null);
headerTable = args.optTable(2, Collections.emptyMap());
headerTable = args.optTable(2, Map.of());
binary = args.optBoolean(3, false);
requestMethod = null;
redirect = true;
@@ -154,11 +153,11 @@ public class HTTPAPI implements ILuaAPI {
if (args.get(0) instanceof Map) {
var options = args.getTable(0);
address = getStringField(options, "url");
headerTable = optTableField(options, "headers", Collections.emptyMap());
headerTable = optTableField(options, "headers", Map.of());
timeoutArg = optRealField(options, "timeout");
} else {
address = args.getString(0);
headerTable = args.optTable(1, Collections.emptyMap());
headerTable = args.optTable(1, Map.of());
timeoutArg = Optional.empty();
}

View File

@@ -12,7 +12,7 @@ import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
import dan200.computercraft.core.apis.handles.HandleGeneric;
import dan200.computercraft.core.methods.ObjectSource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
@@ -74,6 +74,6 @@ public class HttpResponseHandle implements ObjectSource {
@Override
public Iterable<Object> getExtra() {
return Collections.singletonList(reader);
return List.of(reader);
}
}

View File

@@ -4,12 +4,12 @@
package dan200.computercraft.core.apis.http.websocket;
import com.google.common.base.Objects;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.apis.http.options.Options;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
@@ -106,12 +106,12 @@ public class WebsocketHandle {
@Override
public MethodResult resume(Object[] event) {
if (event.length >= 3 && Objects.equal(event[0], MESSAGE_EVENT) && Objects.equal(event[1], address)) {
if (event.length >= 3 && Objects.equals(event[0], MESSAGE_EVENT) && Objects.equals(event[1], address)) {
return MethodResult.of(Arrays.copyOfRange(event, 2, event.length));
} else if (event.length >= 2 && Objects.equal(event[0], CLOSE_EVENT) && Objects.equal(event[1], address) && websocket.isClosed()) {
} else if (event.length >= 2 && Objects.equals(event[0], CLOSE_EVENT) && Objects.equals(event[1], address) && websocket.isClosed()) {
// If the socket is closed abort.
return MethodResult.of();
} else if (event.length >= 2 && timeoutId != -1 && Objects.equal(event[0], TIMER_EVENT)
} else if (event.length >= 2 && timeoutId != -1 && Objects.equals(event[0], TIMER_EVENT)
&& event[1] instanceof Number id && id.intValue() == timeoutId) {
// If we received a matching timer event then abort.
return MethodResult.of();

View File

@@ -9,7 +9,7 @@ import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
import dan200.computercraft.core.methods.ObjectSource;
import java.nio.channels.SeekableByteChannel;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
@@ -42,6 +42,6 @@ public class TransferredFile implements ObjectSource {
@Override
public Iterable<Object> getExtra() {
return Collections.singleton(handle);
return List.of(handle);
}
}

View File

@@ -6,6 +6,7 @@ package dan200.computercraft.core.apis.transfer;
import dan200.computercraft.api.lua.LuaFunction;
import javax.annotation.Nullable;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -18,15 +19,19 @@ public class TransferredFiles {
public static final String EVENT = "file_transfer";
private final AtomicBoolean consumed = new AtomicBoolean(false);
private final Runnable onConsumed;
private final @Nullable Runnable onConsumed;
private final List<TransferredFile> files;
public TransferredFiles(List<TransferredFile> files, Runnable onConsumed) {
public TransferredFiles(List<TransferredFile> files, @Nullable Runnable onConsumed) {
this.files = files;
this.onConsumed = onConsumed;
}
public TransferredFiles(List<TransferredFile> files) {
this(files, null);
}
/**
* All the files that are being transferred to this computer.
*
@@ -40,6 +45,6 @@ public class TransferredFiles {
private void consumed() {
if (consumed.getAndSet(true)) return;
onConsumed.run();
if (onConsumed != null) onConsumed.run();
}
}

View File

@@ -11,37 +11,31 @@ import com.google.common.primitives.Primitives;
import com.google.common.reflect.TypeToken;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.methods.LuaMethod;
import org.objectweb.asm.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.lang.constant.ConstantDescs;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Optional;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Function;
import static org.objectweb.asm.Opcodes.*;
/**
* The underlying generator for {@link LuaFunction}-annotated methods.
* <p>
* The constructor {@link Generator#Generator(Class, List, Function)} takes in the type of interface to generate (i.e.
* {@link LuaMethod}), the context arguments for this function (in the case of {@link LuaMethod}, this will just be
* {@link ILuaContext}) and a "wrapper" function to lift a function to execute on the main thread.
* The constructor {@link Generator#Generator(List, Function, Function)} takes in the type of interface to generate
* (i.e. {@link LuaMethod}), the context arguments for this function (in the case of {@link LuaMethod}, this will just
* be {@link ILuaContext}), a factory function (which invokes a method handle), and a "wrapper" function to lift a
* function to execute on the main thread.
* <p>
* The generated class then implements this interface - the {@code apply} method calls the appropriate methods on
* {@link IArguments} to extract the arguments, and then calls the original method.
* <p>
* As the method is not guaranteed to come from the same classloader, we cannot call the method directly, as that may
* result in linkage errors. We instead inject a {@link MethodHandle} into the class as a dynamic constant, and then
* call the method with {@link MethodHandle#invokeExact(Object...)}. The method handle is constant, and so this has
* equivalent performance to the direct call.
* For each input function, the generator then fabricates a {@link MethodHandle} which performs the argument validation,
* and then calls the factory function to convert it to the desired interface.
*
* @param <T> The type of the interface the generated classes implement.
*/
@@ -49,47 +43,87 @@ final class Generator<T> {
private static final Logger LOG = LoggerFactory.getLogger(Generator.class);
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final String METHOD_NAME = "apply";
private static final String[] EXCEPTIONS = new String[]{ Type.getInternalName(LuaException.class) };
private static final MethodHandle METHOD_RESULT_OF_VOID, METHOD_RESULT_OF_ONE, METHOD_RESULT_OF_MANY;
private static final String INTERNAL_METHOD_RESULT = Type.getInternalName(MethodResult.class);
private static final String DESC_METHOD_RESULT = Type.getDescriptor(MethodResult.class);
private static final Map<Class<?>, ArgMethods> argMethods;
private static final ArgMethods ARG_TABLE_UNSAFE;
private static final MethodHandle ARG_GET_OBJECT, ARG_GET_ENUM, ARG_OPT_ENUM, ARG_GET_STRING_COERCED;
private static final String INTERNAL_ARGUMENTS = Type.getInternalName(IArguments.class);
private static final String DESC_ARGUMENTS = Type.getDescriptor(IArguments.class);
private record ArgMethods(MethodHandle get, MethodHandle opt) {
public static ArgMethods of(Class<?> type, String name) throws ReflectiveOperationException {
return new ArgMethods(
LOOKUP.findVirtual(IArguments.class, "get" + name, MethodType.methodType(type, int.class)),
LOOKUP.findVirtual(IArguments.class, "opt" + name, MethodType.methodType(Optional.class, int.class))
);
}
}
private static final String INTERNAL_COERCED = Type.getInternalName(Coerced.class);
static void addArgType(Map<Class<?>, ArgMethods> types, Class<?> type, String name) throws ReflectiveOperationException {
types.put(type, ArgMethods.of(type, name));
}
private static final ConstantDynamic METHOD_CONSTANT = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), new Handle(
H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classData",
MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class).descriptorString(), false
));
static {
try {
METHOD_RESULT_OF_VOID = LOOKUP.findStatic(MethodResult.class, "of", MethodType.methodType(MethodResult.class));
METHOD_RESULT_OF_ONE = LOOKUP.findStatic(MethodResult.class, "of", MethodType.methodType(MethodResult.class, Object.class));
METHOD_RESULT_OF_MANY = LOOKUP.findStatic(MethodResult.class, "of", MethodType.methodType(MethodResult.class, Object[].class));
Map<Class<?>, ArgMethods> argMethodMap = new HashMap<>();
addArgType(argMethodMap, int.class, "Int");
addArgType(argMethodMap, boolean.class, "Boolean");
addArgType(argMethodMap, double.class, "Double");
addArgType(argMethodMap, long.class, "Long");
addArgType(argMethodMap, Map.class, "Table");
addArgType(argMethodMap, String.class, "String");
addArgType(argMethodMap, ByteBuffer.class, "Bytes");
argMethods = Map.copyOf(argMethodMap);
ARG_TABLE_UNSAFE = ArgMethods.of(LuaTable.class, "TableUnsafe");
ARG_GET_OBJECT = LOOKUP.findVirtual(IArguments.class, "get", MethodType.methodType(Object.class, int.class));
ARG_GET_ENUM = LOOKUP.findVirtual(IArguments.class, "getEnum", MethodType.methodType(Enum.class, int.class, Class.class));
ARG_OPT_ENUM = LOOKUP.findVirtual(IArguments.class, "optEnum", MethodType.methodType(Optional.class, int.class, Class.class));
// Create a new Coerced<>(args.getStringCoerced(_)) function.
ARG_GET_STRING_COERCED = MethodHandles.filterReturnValue(
setReturn(LOOKUP.findVirtual(IArguments.class, "getStringCoerced", MethodType.methodType(String.class, int.class)), Object.class),
LOOKUP.findConstructor(Coerced.class, MethodType.methodType(void.class, Object.class))
);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private final Class<T> base;
private final List<Class<?>> context;
private final List<Class<?>> contextWithArguments;
private final MethodHandle argumentGetter;
private final List<MethodHandle> contextGetters;
private final String[] interfaces;
private final String methodDesc;
private final String classPrefix;
private final Function<MethodHandle, T> factory;
private final Function<T, T> wrap;
private final LoadingCache<Method, Optional<T>> methodCache = CacheBuilder
.newBuilder()
.build(CacheLoader.from(catching(this::build, Optional.empty())));
Generator(Class<T> base, List<Class<?>> context, Function<T, T> wrap) {
this.base = base;
Generator(List<Class<?>> context, Function<MethodHandle, T> factory, Function<T, T> wrap) {
this.context = context;
interfaces = new String[]{ Type.getInternalName(base) };
this.factory = factory;
this.wrap = wrap;
var methodDesc = new StringBuilder().append("(Ljava/lang/Object;");
for (var klass : context) methodDesc.append(Type.getDescriptor(klass));
methodDesc.append(DESC_ARGUMENTS).append(")").append(DESC_METHOD_RESULT);
this.methodDesc = methodDesc.toString();
var contextWithArguments = this.contextWithArguments = new ArrayList<>(context.size() + 1);
contextWithArguments.addAll(context);
contextWithArguments.add(IArguments.class);
classPrefix = Generator.class.getPackageName() + "." + base.getSimpleName() + "$";
// Prepare a series of getters of the type (context..., IArguments) -> _ (or some prefix of this), for
// extracting a single context value.
argumentGetter = MethodHandles.dropArguments(MethodHandles.identity(IArguments.class), 0, context);
var contextGetters = this.contextGetters = new ArrayList<>(context.size());
for (var i = 0; i < context.size(); i++) {
var getter = MethodHandles.identity(context.get(i));
if (i > 0) getter = MethodHandles.dropArguments(getter, 0, contextWithArguments.subList(0, i));
contextGetters.add(getter);
}
}
Optional<T> getMethod(Method method) {
@@ -131,184 +165,144 @@ final class Generator<T> {
return Optional.empty();
}
// We have some rather ugly handling of static methods in both here and the main generate function. Static methods
// only come from generic sources, so this should be safe.
var target = Modifier.isStatic(modifiers) ? method.getParameterTypes()[0] : method.getDeclaringClass();
try {
var handle = LOOKUP.unreflect(method);
var originalHandle = LOOKUP.unreflect(method);
// Convert the handle from one of the form (target, ...) -> ret type to (Object, ...) -> Object. This both
// handles the boxing of primitives for us, and ensures our bytecode does not reference any external types.
// We could handle the conversion to MethodResult here too, but it doesn't feel worth it.
var widenedHandle = handle.asType(widenMethodType(handle.type(), target));
List<Type> parameters;
if (Modifier.isStatic(modifiers)) {
var allParameters = method.getGenericParameterTypes();
parameters = Arrays.asList(allParameters).subList(1, allParameters.length);
} else {
parameters = Arrays.asList(method.getGenericParameterTypes());
}
var bytes = generate(classPrefix + method.getName(), target, method, widenedHandle.type().descriptorString(), annotation.unsafe());
if (bytes == null) return Optional.empty();
var handle = buildMethodHandle(method, originalHandle, parameters, annotation.unsafe());
if (handle == null) return Optional.empty();
var klass = LOOKUP.defineHiddenClassWithClassData(bytes, widenedHandle, true).lookupClass();
var instance = klass.asSubclass(base).getDeclaredConstructor().newInstance();
var instance = factory.apply(handle);
return Optional.of(annotation.mainThread() ? wrap.apply(instance) : instance);
} catch (ReflectiveOperationException | ClassFormatError | RuntimeException e) {
} catch (ReflectiveOperationException | RuntimeException e) {
LOG.error("Error generating wrapper for {}.", name, e);
return Optional.empty();
}
}
private static MethodType widenMethodType(MethodType source, Class<?> target) {
// Treat the target argument as just Object - we'll do the cast in the method handle.
var args = source.parameterArray();
for (var i = 0; i < args.length; i++) {
if (args[i] == target) args[i] = Object.class;
}
// And convert the return value to Object if needed.
var ret = source.returnType();
return ret == void.class || ret == MethodResult.class || ret == Object[].class
? MethodType.methodType(ret, args)
: MethodType.methodType(Object.class, args);
}
/**
* Convert the given handle from type {@code (target, args...) -> ret} to {@code (Object, context..., IArguments) -> MethodResult},
* inserting calls to {@link IArguments}'s getters, and wrapping the result with {@link MethodResult#of()}.
*
* @param method The original method, for error reporting.
* @param handle The method handle to wrap.
* @param parameterTypes The generic parameter types to this method. This should have the same type as the {@code handle}.
* @param unsafe Whether to allow unsafe argument getters.
* @return The wrapped method handle.
*/
@Nullable
private byte[] generate(String className, Class<?> target, Method targetMethod, String targetDescriptor, boolean unsafe) {
var internalName = className.replace(".", "/");
// Construct a public final class which extends Object and implements MethodInstance.Delegate
var cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cw.visit(V17, ACC_PUBLIC | ACC_FINAL, internalName, null, "java/lang/Object", interfaces);
cw.visitSource("CC generated method", null);
{ // Constructor just invokes super.
var mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mw.visitCode();
mw.visitVarInsn(ALOAD, 0);
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mw.visitInsn(RETURN);
mw.visitMaxs(0, 0);
mw.visitEnd();
private MethodHandle buildMethodHandle(Member method, MethodHandle handle, List<Type> parameterTypes, boolean unsafe) {
if (handle.type().parameterCount() != parameterTypes.size() + 1) {
throw new IllegalArgumentException("Argument lists are mismatched");
}
{
var mw = cw.visitMethod(ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS);
mw.visitCode();
// We start off with a method handle of type (target, args...) -> _. We then append the context and IArguments
// to the end, leaving a handle with type (target, args..., context..., IArguments) -> _.
handle = MethodHandles.dropArguments(handle, handle.type().parameterCount(), contextWithArguments);
mw.visitLdcInsn(METHOD_CONSTANT);
// Then for each argument, generate a method handle of type (context..., IArguments) -> _, which is used to
// extract this argument.
var argCount = 0;
List<MethodHandle> argSelectors = new ArrayList<>(parameterTypes.size());
for (var paramType : parameterTypes) {
var paramClass = Reflect.getRawType(method, paramType, true);
if (paramClass == null) return null;
// If we're an instance method, load the target as the first argument.
if (!Modifier.isStatic(targetMethod.getModifiers())) mw.visitVarInsn(ALOAD, 1);
var argIndex = 0;
for (var genericArg : targetMethod.getGenericParameterTypes()) {
var loadedArg = loadArg(mw, target, targetMethod, unsafe, genericArg, argIndex);
if (loadedArg == null) return null;
if (loadedArg) argIndex++;
}
mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", targetDescriptor, false);
// We allow a reasonable amount of flexibility on the return value's type. Alongside the obvious MethodResult,
// we convert basic types into an immediate result.
var ret = targetMethod.getReturnType();
if (ret != MethodResult.class) {
if (ret == void.class) {
mw.visitMethodInsn(INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "()" + DESC_METHOD_RESULT, false);
} else if (ret == Object[].class) {
mw.visitMethodInsn(INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "([Ljava/lang/Object;)" + DESC_METHOD_RESULT, false);
// We first generate a method handle of type (context..., IArguments) -> _, which is used to extract this
// argument.
MethodHandle argSelector;
if (paramClass == IArguments.class) {
argSelector = argumentGetter;
} else {
var idx = context.indexOf(paramClass);
if (idx >= 0) {
argSelector = contextGetters.get(idx);
} else {
mw.visitMethodInsn(INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "(Ljava/lang/Object;)" + DESC_METHOD_RESULT, false);
var selector = loadArg(method, unsafe, paramClass, paramType, argCount++);
if (selector == null) return null;
argSelector = MethodHandles.filterReturnValue(argumentGetter, selector);
}
}
mw.visitInsn(ARETURN);
mw.visitMaxs(0, 0);
mw.visitEnd();
argSelectors.add(argSelector);
}
cw.visitEnd();
// Fold over the original method's arguments, excluding the target in reverse. For each argument, we reduce
// a method of type type (target, args..., arg_n, context..., IArguments) -> _ to (target, args..., context..., IArguments) -> _
// until eventually we've flattened the whole list.
for (var i = parameterTypes.size() - 1; i >= 0; i--) {
handle = MethodHandles.foldArguments(handle, i + 1, argSelectors.get(i));
}
return cw.toByteArray();
// Then cast the target to Object, so it's compatible with the desired type.
handle = handle.asType(handle.type().changeParameterType(0, Object.class));
// Finally wrap the returned value into a MethodResult.
var type = handle.type();
var ret = type.returnType();
if (ret == MethodResult.class) {
return handle;
} else if (ret == void.class) {
return MethodHandles.filterReturnValue(handle, METHOD_RESULT_OF_VOID);
} else if (ret == Object[].class) {
return MethodHandles.filterReturnValue(handle, METHOD_RESULT_OF_MANY);
} else {
return MethodHandles.filterReturnValue(handle.asType(type.changeReturnType(Object.class)), METHOD_RESULT_OF_ONE);
}
}
@Nullable
private Boolean loadArg(MethodVisitor mw, Class<?> target, Method method, boolean unsafe, java.lang.reflect.Type genericArg, int argIndex) {
if (genericArg == target) {
mw.visitVarInsn(ALOAD, 1);
return false;
}
var arg = Reflect.getRawType(method, genericArg, true);
if (arg == null) return null;
if (arg == IArguments.class) {
mw.visitVarInsn(ALOAD, 2 + context.size());
return false;
}
var idx = context.indexOf(arg);
if (idx >= 0) {
mw.visitVarInsn(ALOAD, 2 + idx);
return false;
}
if (arg == Coerced.class) {
private static MethodHandle loadArg(Member method, boolean unsafe, Class<?> argType, Type genericArg, int argIndex) {
if (argType == Coerced.class) {
var klass = Reflect.getRawType(method, TypeToken.of(genericArg).resolveType(Reflect.COERCED_IN).getType(), false);
if (klass == null) return null;
if (klass == String.class) {
mw.visitTypeInsn(NEW, INTERNAL_COERCED);
mw.visitInsn(DUP);
mw.visitVarInsn(ALOAD, 2 + context.size());
Reflect.loadInt(mw, argIndex);
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "getStringCoerced", "(I)Ljava/lang/String;", true);
mw.visitMethodInsn(INVOKESPECIAL, INTERNAL_COERCED, "<init>", "(Ljava/lang/Object;)V", false);
return true;
}
if (klass == String.class) return MethodHandles.insertArguments(ARG_GET_STRING_COERCED, 1, argIndex);
}
if (arg == Optional.class) {
var klass = Reflect.getRawType(method, TypeToken.of(genericArg).resolveType(Reflect.OPTIONAL_IN).getType(), false);
if (klass == null) return null;
if (argType == Optional.class) {
var optType = Reflect.getRawType(method, TypeToken.of(genericArg).resolveType(Reflect.OPTIONAL_IN).getType(), false);
if (optType == null) return null;
if (Enum.class.isAssignableFrom(klass) && klass != Enum.class) {
mw.visitVarInsn(ALOAD, 2 + context.size());
Reflect.loadInt(mw, argIndex);
mw.visitLdcInsn(Type.getType(klass));
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "optEnum", "(ILjava/lang/Class;)Ljava/util/Optional;", true);
return true;
if (Enum.class.isAssignableFrom(optType) && optType != Enum.class) {
return MethodHandles.insertArguments(ARG_OPT_ENUM, 1, argIndex, optType);
}
var name = Reflect.getLuaName(Primitives.unwrap(klass), unsafe);
if (name != null) {
mw.visitVarInsn(ALOAD, 2 + context.size());
Reflect.loadInt(mw, argIndex);
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "opt" + name, "(I)Ljava/util/Optional;", true);
return true;
}
var getter = getArgMethods(Primitives.unwrap(optType), unsafe);
if (getter != null) return MethodHandles.insertArguments(getter.opt(), 1, argIndex);
}
if (Enum.class.isAssignableFrom(arg) && arg != Enum.class) {
mw.visitVarInsn(ALOAD, 2 + context.size());
Reflect.loadInt(mw, argIndex);
mw.visitLdcInsn(Type.getType(arg));
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "getEnum", "(ILjava/lang/Class;)Ljava/lang/Enum;", true);
mw.visitTypeInsn(CHECKCAST, Type.getInternalName(arg));
return true;
if (Enum.class.isAssignableFrom(argType) && argType != Enum.class) {
return setReturn(MethodHandles.insertArguments(ARG_GET_ENUM, 1, argIndex, argType), argType);
}
var name = arg == Object.class ? "" : Reflect.getLuaName(arg, unsafe);
if (name != null) {
if (Reflect.getRawType(method, genericArg, false) == null) return null;
if (argType == Object.class) return MethodHandles.insertArguments(ARG_GET_OBJECT, 1, argIndex);
mw.visitVarInsn(ALOAD, 2 + context.size());
Reflect.loadInt(mw, argIndex);
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "get" + name, "(I)" + Type.getDescriptor(arg), true);
return true;
}
// Check we don't have a non-wildcard generic.
if (Reflect.getRawType(method, genericArg, false) == null) return null;
LOG.error("Unknown parameter type {} for method {}.{}.",
arg.getName(), method.getDeclaringClass().getName(), method.getName());
var getter = getArgMethods(argType, unsafe);
if (getter != null) return MethodHandles.insertArguments(getter.get(), 1, argIndex);
LOG.error("Unknown parameter type {} for method {}.{}.", argType.getName(), method.getDeclaringClass().getName(), method.getName());
return null;
}
private static MethodHandle setReturn(MethodHandle handle, Class<?> retTy) {
return handle.asType(handle.type().changeReturnType(retTy));
}
private static @Nullable ArgMethods getArgMethods(Class<?> type, boolean unsafe) {
var getter = argMethods.get(type);
if (getter != null) return getter;
if (type == LuaTable.class && unsafe) return ARG_TABLE_UNSAFE;
return null;
}

View File

@@ -6,6 +6,7 @@ package dan200.computercraft.core.asm;
import dan200.computercraft.api.lua.IDynamicLuaObject;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.core.ComputerContext;
import dan200.computercraft.core.methods.LuaMethod;
import dan200.computercraft.core.methods.MethodSupplier;
@@ -20,7 +21,14 @@ import java.util.Objects;
* method supplier}. It should not be used directly.
*/
public final class LuaMethodSupplier {
private static final Generator<LuaMethod> GENERATOR = new Generator<>(LuaMethod.class, List.of(ILuaContext.class),
private static final Generator<LuaMethod> GENERATOR = new Generator<>(List.of(ILuaContext.class),
m -> (target, context, args) -> {
try {
return (MethodResult) m.invokeExact(target, context, args);
} catch (Throwable t) {
throw ResultHelpers.throwUnchecked(t);
}
},
m -> (target, context, args) -> {
var escArgs = args.escapes();
return context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, escArgs)));

View File

@@ -4,9 +4,6 @@
package dan200.computercraft.core.asm;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.PeripheralType;
@@ -23,7 +20,6 @@ import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import static dan200.computercraft.core.asm.Generator.catching;
@@ -36,9 +32,14 @@ final class MethodSupplierImpl<T> implements MethodSupplier<T> {
private final IntCache<T> dynamic;
private final Function<Object, String[]> dynamicMethods;
private final LoadingCache<Class<?>, List<NamedMethod<T>>> classCache = CacheBuilder
.newBuilder()
.build(CacheLoader.from(catching(this::getMethodsImpl, List.of())));
private final ClassValue<List<NamedMethod<T>>> classCache = new ClassValue<>() {
private final Function<Class<?>, List<NamedMethod<T>>> getter = catching(MethodSupplierImpl.this::getMethodsImpl, List.of());
@Override
protected List<NamedMethod<T>> computeValue(Class<?> type) {
return getter.apply(type);
}
};
MethodSupplierImpl(
List<GenericMethod> genericMethods,
@@ -93,12 +94,7 @@ final class MethodSupplierImpl<T> implements MethodSupplier<T> {
@VisibleForTesting
List<NamedMethod<T>> getMethods(Class<?> klass) {
try {
return classCache.get(klass);
} catch (ExecutionException e) {
LOG.error("Error getting methods for {}.", klass.getName(), e.getCause());
return List.of();
}
return classCache.get(klass);
}
private List<NamedMethod<T>> getMethodsImpl(Class<?> klass) {
@@ -148,5 +144,4 @@ final class MethodSupplierImpl<T> implements MethodSupplier<T> {
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More