1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-22 09:27:39 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
SquidDev
6ac1c0e944 Basic network visualiser 2019-02-11 09:05:27 +00:00
523 changed files with 8584 additions and 17023 deletions

View File

@@ -1,15 +1,16 @@
---
name: Bug report
about: Report some misbehaviour in the mod
labels: bug
---
<!--
## Before reporting
- Search for the bug on the issue tracker. Make sure to look at closed issues too!
- Search for the bug both here and [on the ComputerCraft issues page](https://github.com/dan200/ComputerCraft/issues?utf8=%E2%9C%93&q=is%3Aissue+)
- If possible, try to reproduce on vanilla ComputerCraft. If it still occurs, [report on the ComputerCraft repo](https://github.com/dan200/ComputerCraft/issues/new) instead.
-->
## Useful information to include:
- Minecraft version
- CC: Tweaked version
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.
- Detailed reproduction steps!** Sometimes I can spot a bug pretty easily, but often it's much more obscure. Anything you can give which will help reproduce it means it'll get fixed quicker.

View File

@@ -1,14 +1,15 @@
---
name: Feature request
about: Suggest an idea or improvement
labels: enhancement
---
<!--
## Before reporting
- Search for the suggestion here. It's possible someone's suggested it before!
- Search for the suggestion both here and [on the ComputerCraft issues page](https://github.com/dan200/ComputerCraft/issues?utf8=%E2%9C%93&q=is%3Aissue+). It's possible someone's suggested it before!
- Unless something is specific to CC:Tweaked, try to [suggest them on the ComputerCraft repo](https://github.com/dan200/ComputerCraft/issues/new). There's a lot more people watching it, so it allows the wider community to contribute.
-->
## Useful information to include:
- Explanation of how the feature/change should work.
- Some rationale/use case for a feature. My general approach to designing new features is to ask yourself "what issue are we trying to solve" and _then_ "is this the best way to solve this issue?".
- Explanation of how the feature/change chould work.
- Some rationale/use case for a feature. I'd like to keep CC:T as minimal

View File

@@ -1,3 +1,9 @@
## A quick checklist
- If there's a existing issue, please link to it. If not, provide fill out the same information you would in a normal issue - reproduction steps for bugs, rationale for use-case.
- If you're working on CraftOS, try to write a few test cases so we can ensure everything continues to work in the future. Tests live in `src/test/resources/test-rom/spec` and can be run with `./gradlew check`.
<!--
Unless this feature is specific to CC:Tweaked, try to [target the original ComputerCraft repo](https://github.com/dan200/ComputerCraft/) instead. There's a lot more people watching it, so it allows the wider community to contribute.
-->
## Useful information to include:
- Brief explanation of the changes you've made.
- Rationale of why this change has been made/reasoning behind it.
The more information you can provide, the easier it is to review something now _and_ to see why a change was made, when the code needs updating in the future.

19
LICENSE-luaj Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2007 LuaJ. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,35 +1,35 @@
# ![CC: Tweaked](logo.png)
[![Current build status](https://travis-ci.org/SquidDev-CC/CC-Tweaked.svg?branch=master)](https://travis-ci.org/SquidDev-CC/CC-Tweaked "Current build status") [![Download CC: Tweaked on CurseForge](https://cf.way2muchnoise.eu/title/cc-tweaked.svg)](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
[![Build Status](https://travis-ci.org/SquidDev-CC/CC-Tweaked.svg?branch=master)](https://travis-ci.org/SquidDev-CC/CC-Tweaked)
CC: Tweaked is a fork of [ComputerCraft](https://github.com/dan200/ComputerCraft), adding programmable computers,
turtles and more to Minecraft.
CC: Tweaked is a fork of ComputerCraft which aims to provide earlier access to the more experimental and in-development
features of the mod. For a more stable experience, I recommend checking out the
[original mod](https://github.com/dan200/ComputerCraft).
## What?
ComputerCraft has always held a fond place in my heart: it's the mod which really got me into Minecraft, and it's the
mod which has kept me playing it for many years. However, development of the original mod has slowed, as the original
developers have had less time to work on the mod, and moved onto other projects and commitments.
CC: Tweaked (or CC:T for short) does not aim to create a competing fork of ComputerCraft, nor am I planning to take it
in in a vastly different direction to the original mod. In fact, CC:T aims to be a nurturing ground for various
features, with a pull request against the original mod being the end goal.
CC: Tweaked (or CC:T for short) is an attempt to continue ComputerCraft's legacy. It's not intended to be a competitor
to CC, nor do I want to take it in a vastly different direction to the original mod. Instead, CC:T focuses on making the
ComputerCraft experience as _solid_ as possible, ironing out any wrinkles that may have developed over time.
CC:T also includes many pull requests from the community which have not yet been merged, offering a large number
of additional bug fixes and features over the original mod.
## Features
CC: Tweaked contains all the features of the latest version of ComputerCraft, as well as numerous fixes, performance
improvements and several nifty additions. I'd recommend checking out [the releases page](https://github.com/SquidDev-CC/CC-Tweaked/releases)
to see the full set of changes, but here's a couple of the more interesting additions:
CC: Tweaked contains all the features of the latest alpha, as well as numerous fixes, performance improvements and
several additional features. I'd recommend checking out [the releases page](https://github.com/SquidDev-CC/CC-Tweaked/releases)
to see the full changes, but here's a couple of the more interesting changes:
- Improvements to the `http` library, including websockets, support for other HTTP methods (`PUT`, `DELETE`, etc...)
and configurable limits on HTTP usage.
- Full-block wired modems, allowing one to wrap non-solid peripherals (such as turtles, or chests if Plethora is
installed).
- Pocket computers can be held like maps, allowing you to view the screen without entering a GUI.
- Printed pages and books can be placed in item frames and held like maps.
- Several profiling and administration tools for server owners, via the `/computercraft` command. This allows operators
to track which computers are hogging resources, turn on and shutdown multiple computers at once and interact with
- Replace LuaJ with Cobalt.
- Allow running multiple computers at the same time.
- Websocket support in the HTTP library.
- Wired modems and cables act more like multiparts.
- Add map-like rendering for pocket computers and printed pages/books.
- Adds the `/computercraft` command, offering various diagnostic tools for server owners. This allows operators to
track which computers are hogging resources, turn on and shutdown multiple computers at once and interact with
computers remotely.
- Closer emulation of standard Lua, adding the `debug` and `io` libraries. This also enables seeking within binary
files, meaning you don't need to read large files into memory.
- Allow running multiple computers on multiple threads, reducing latency on worlds with many computers.
- Add full-block wired modems, allowing one to wrap non-solid peripherals (such as turtles, or chests if Plethora is
installed).
- Extended binary file handles. They support file seeking, and reading new lines, allowing full (and accurate)
emulation of the standard Lua `io` library.
## Relation to CCTweaks?
This mod has nothing to do with CCTweaks, though there is no denying the name is a throwback to it. That being said,
@@ -37,39 +37,13 @@ several features have been included, such as full block modems, the Cobalt runti
computers.
## Contributing
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. In order to start helping
develop CC:T, you'll need to follow these steps:
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you do wish to contribute
code, do consider submitting it to the ComputerCraft repository first.
That being said, in order to start helping develop CC:T, you'll need to follow these steps:
- **Clone the repository:** `git clone https://github.com/SquidDev-CC/CC-Tweaked.git && cd CC-Tweaked`
- **Setup Forge:** `./gradlew setupDecompWorkspace`
- **Test your changes:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
## Community
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
ComputerCraft we have a [forum](https://forums.computercraft.cc/) and [Discord guild](https://discord.gg/H2UyJXe)!
There's also a fairly populated, albeit quiet [IRC channel](http://webchat.esper.net/?channels=#computercraft), if
that's more your cup of tea.
I'd generally recommend you don't contact me directly (email, DM, etc...) unless absolutely necessary (i.e. in order to
report exploits). You'll get a far quicker response if you ask the whole community!
## Using
If you want to depend on CC: Tweaked, we have a maven repo. However, you should be wary that some functionality is only
exposed by CC:T's API and not vanilla ComputerCraft. If you wish to support all variations of ComputerCraft, I recommend
using [cc.crzd.me's maven](https://cc.crzd.me/maven/) instead.
```groovy
dependencies {
maven { url 'https://squiddev.cc/maven/' }
}
dependencies {
implementation "org.squiddev:cc-tweaked-${mc_version}:${cct_version}"
}
```
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
exposing more features.

View File

@@ -9,16 +9,13 @@ buildscript {
}
}
dependencies {
classpath 'com.google.code.gson:gson:2.8.1'
classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT'
classpath 'net.sf.proguard:proguard-gradle:6.1.0beta1'
classpath 'org.ajoberstar.grgit:grgit-gradle:3.0.0'
}
}
plugins {
id 'com.matthewprenger.cursegradle' version '1.2.0'
id "com.github.breadmoirai.github-release" version "2.2.4"
id 'com.matthewprenger.cursegradle' version '1.0.10'
}
apply plugin: 'net.minecraftforge.gradle.forge'
@@ -26,37 +23,35 @@ apply plugin: 'org.ajoberstar.grgit'
apply plugin: 'maven-publish'
apply plugin: 'maven'
version = mod_version
version = "1.80pr1.14"
group = "org.squiddev"
archivesBaseName = "cc-tweaked-${mc_version}"
archivesBaseName = "cc-tweaked"
minecraft {
version = "${mc_version}-${forge_version}"
version = "1.12.2-14.23.4.2749"
runDir = "run"
replace '${version}', mod_version
replace '${version}', project.version
mappings = mappings_version
makeObfSourceJar = false
// the mappings can be changed at any time, and must be in the following format.
// snapshot_YYYYMMDD snapshot are built nightly.
// stable_# stables are built at the discretion of the MCP team.
// Use non-default mappings at your own risk. they may not allways work.
// simply re-run your setup task after changing the mappings to update your workspace.
mappings = "snapshot_20180724"
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
}
repositories {
maven {
name "JEI"
url "http://dvs1.progwml6.com/files/maven"
name = "JEI"
url = "http://dvs1.progwml6.com/files/maven"
}
maven {
name "SquidDev"
url "https://squiddev.cc/maven"
}
ivy {
name "Charset"
artifactPattern "https://asie.pl/files/mods/Charset/LibOnly/[module]-[revision](-[classifier]).[ext]"
}
maven {
name "Amadornes"
url "http://maven.amadornes.com/"
name = "squiddev"
url = "https://squiddev.cc/maven"
}
ivy { artifactPattern "https://asie.pl/files/mods/Charset/LibOnly/[module]-[revision](-[classifier]).[ext]" }
}
configurations {
@@ -66,16 +61,14 @@ configurations {
}
dependencies {
deobfProvided "mezz.jei:jei_1.12.2:4.15.0.269:api"
deobfProvided "mezz.jei:jei_1.12.2:4.8.5.159:api"
deobfProvided "pl.asie:Charset-Lib:0.5.4.6"
deobfProvided "MCMultiPart2:MCMultiPart:2.5.3"
runtime "mezz.jei:jei_1.12.2:4.15.0.269"
runtime "mezz.jei:jei_1.12.2:4.8.5.159"
shade 'org.squiddev:Cobalt:0.5.0-SNAPSHOT'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
testCompile 'junit:junit:4.11'
deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
}
@@ -91,68 +84,20 @@ jar {
attributes('FMLAT': 'computercraft_at.cfg')
}
from (sourceSets.main.allSource) {
into("docs", { from (javadoc.destinationDir) })
into("api", { from (sourceSets.main.allSource) {
include "dan200/computercraft/api/**/*.java"
}
}})
from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) }
}
import java.nio.charset.StandardCharsets
import java.nio.file.*
import java.util.zip.*
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import org.ajoberstar.grgit.Grgit
import proguard.gradle.ProGuardTask
task proguard(type: ProGuardTask, dependsOn: jar) {
description "Removes unused shadowed classes from the jar"
group "compact"
injars jar.archivePath
outjars "${jar.archivePath.absolutePath.replace(".jar", "")}-min.jar"
// Add the main runtime jar and all non-shadowed dependencies
libraryjars "${System.getProperty('java.home')}/lib/rt.jar"
doFirst {
sourceSets.main.compileClasspath
.filter { !it.name.contains("Cobalt") }
.each { libraryjars it }
}
// We want to avoid as much obfuscation as possible. We're only doing this to shrink code size.
dontobfuscate; dontoptimize; keepattributes; keepparameternames
// Proguard will remove directories by default, but that breaks JarMount.
keepdirectories 'assets/computercraft/lua**'
// Preserve ComputerCraft classes - we only want to strip shadowed files.
keep 'class dan200.computercraft.** { *; }'
// Preserve the constructors in Cobalt library class, as we init them via reflection
keepclassmembers 'class org.squiddev.cobalt.lib.** { <init>(...); }'
}
task proguardMove(dependsOn: proguard) {
description "Replace the original jar with the minified version"
group "compact"
doLast {
Files.move(
file("${jar.archivePath.absolutePath.replace(".jar", "")}-min.jar").toPath(),
file(jar.archivePath).toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
}
reobfJar.dependsOn proguardMove
processResources {
inputs.property "version", mod_version
inputs.property "mcversion", mc_version
inputs.property "version", project.version
inputs.property "mcversion", project.minecraft.version
def hash = 'none'
Set<String> contributors = []
@@ -173,9 +118,9 @@ processResources {
include 'mcmod.info'
include 'assets/computercraft/lua/rom/help/credits.txt'
expand 'version': mod_version,
'mcversion': mc_version,
'gitcontributors': contributors.sort(false, String.CASE_INSENSITIVE_ORDER).join('\n')
expand 'version':project.version,
'mcversion':project.minecraft.version,
'gitcontributors':contributors.sort(false, String.CASE_INSENSITIVE_ORDER).join('\n')
}
from(sourceSets.main.resources.srcDirs) {
@@ -184,98 +129,13 @@ processResources {
}
}
task compressJson(dependsOn: extractAnnotationsJar) {
group "compact"
description "Minifies all JSON files, stripping whitespace"
def jarPath = file(jar.archivePath)
def tempPath = File.createTempFile("input", ".jar", temporaryDir)
tempPath.deleteOnExit()
def gson = new GsonBuilder().create()
doLast {
// Copy over all files in the current jar to the new one, running json files from GSON. As pretty printing
// is turned off, they should be minified.
new ZipFile(jarPath).withCloseable { inJar ->
new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tempPath))).withCloseable { outJar ->
inJar.entries().each { entry ->
if(entry.directory) {
outJar.putNextEntry(entry)
} else if(!entry.name.endsWith(".json")) {
outJar.putNextEntry(entry)
inJar.getInputStream(entry).withCloseable { outJar << it }
} else {
ZipEntry newEntry = new ZipEntry(entry.name)
newEntry.setTime(entry.time)
outJar.putNextEntry(newEntry)
def element = inJar.getInputStream(entry).withCloseable { gson.fromJson(it.newReader("UTF8"), JsonElement.class) }
outJar.write(gson.toJson(element).getBytes(StandardCharsets.UTF_8))
}
}
}
}
// And replace the original jar again
Files.move(tempPath.toPath(), jarPath.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
}
assemble.dependsOn compressJson
task checkRelease {
group "upload"
description "Verifies that everything is ready for a release"
inputs.property "version", mod_version
inputs.file("src/main/resources/assets/computercraft/lua/rom/help/changelog.txt")
inputs.file("src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt")
doLast {
def ok = true
// Check we're targetting the current version
def whatsnew = new File("src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt").readLines()
if (whatsnew[0] != "New features in CC: Tweaked $mod_version") {
ok = false
project.logger.error("Expected `whatsnew.txt' to target $mod_version.")
}
// Check "read more" exists and trim it
def idx = whatsnew.findIndexOf { it == 'Type "help changelog" to see the full version history.' }
if (idx == -1) {
ok = false
project.logger.error("Must mention the changelog in whatsnew.txt")
} else {
whatsnew = whatsnew.getAt(0 ..< idx)
}
// Check whatsnew and changelog match.
def versionChangelog = "# " + whatsnew.join("\n")
def changelog = new File("src/main/resources/assets/computercraft/lua/rom/help/changelog.txt").getText()
if (!changelog.startsWith(versionChangelog)) {
ok = false
project.logger.error("whatsnew and changelog are not in sync")
}
if (!ok) throw new IllegalStateException("Could not check release")
}
}
curseforge {
apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : ''
project {
id = '282001'
releaseType = 'release'
changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${mc_version}-${mod_version})."
relations {
incompatible "computercraft"
}
releaseType = 'beta'
changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${project.version})."
}
}
@@ -303,22 +163,22 @@ uploadArchives {
pom.project {
name 'CC: Tweaked'
packaging 'jar'
description 'CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.'
description 'A fork of ComputerCraft which aims to provide earlier access to the more experimental and in-development features of the mod.'
url 'https://github.com/SquidDev-CC/CC-Tweaked'
scm {
url 'https://github.com/SquidDev-CC/CC-Tweaked.git'
url 'https://github.com/dan200/ComputerCraft.git'
}
issueManagement {
system 'github'
url 'https://github.com/SquidDev-CC/CC-Tweaked/issues'
url 'https://github.com/dan200/ComputerCraft/issues'
}
licenses {
license {
name 'ComputerCraft Public License, Version 1.0'
url 'https://github.com/SquidDev-CC/CC-Tweaked/blob/master/LICENSE'
url 'https://github.com/dan200/ComputerCraft/blob/master/LICENSE'
distribution 'repo'
}
}
@@ -332,41 +192,9 @@ uploadArchives {
}
}
githubRelease {
token project.hasProperty('githubApiKey') ? project.githubApiKey : ''
owner 'SquidDev-CC'
repo 'CC-Tweaked'
targetCommitish { Grgit.open(dir: '.').branch.current().name }
tagName "v${mc_version}-${mod_version}"
releaseName "[${mc_version}] ${mod_version}"
body {
"## " + new File("src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt")
.readLines()
.takeWhile { it != 'Type "help changelog" to see the full version history.' }
.join("\n").trim()
}
prerelease false
}
def uploadTasks = ["uploadArchives", "curseforge", "githubRelease"]
uploadTasks.forEach { tasks.getByName(it).dependsOn checkRelease }
task uploadAll(dependsOn: uploadTasks) {
group "upload"
description "Uploads to all repositories (Maven, Curse, GitHub release)"
}
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint" << "-Xlint:-processing" << "-Werror"
options.compilerArgs << "-Xlint"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
# Mod properties
mod_version=1.83.1
# Minecraft properties
mc_version=1.12.2
forge_version=14.23.4.2749
mappings_version=snapshot_20180724

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip

View File

@@ -1 +1 @@
rootProject.name = "cc-tweaked-${mc_version}"
rootProject.name = 'cc-tweaked'

View File

@@ -1,30 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.network.NetworkCheckHandler;
import net.minecraftforge.fml.relauncher.Side;
import java.util.Map;
/**
* A stub mod for CC: Tweaked. This doesn't have any functionality (everything of note is done in
* {@link ComputerCraft}), but people may depend on this if they require CC: Tweaked functionality.
*/
@Mod(
modid = "cctweaked", name = ComputerCraft.NAME, version = ComputerCraft.VERSION,
acceptableRemoteVersions = "*"
)
public class CCTweaked
{
@NetworkCheckHandler
public boolean onNetworkConnect( Map<String, String> mods, Side side )
{
return true;
}
}

View File

@@ -24,15 +24,18 @@ import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.AddressPredicate;
import dan200.computercraft.core.apis.ApiFactories;
import dan200.computercraft.core.apis.http.websocket.Websocket;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.filesystem.ComboMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.*;
import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
import dan200.computercraft.shared.computer.blocks.BlockComputer;
import dan200.computercraft.shared.computer.blocks.TileComputer;
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.items.ItemCommandComputer;
import dan200.computercraft.shared.computer.items.ItemComputer;
@@ -40,19 +43,24 @@ import dan200.computercraft.shared.media.items.ItemDiskExpanded;
import dan200.computercraft.shared.media.items.ItemDiskLegacy;
import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.media.items.ItemTreasureDisk;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.peripheral.common.BlockPeripheral;
import dan200.computercraft.shared.peripheral.common.ItemPeripheral;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable;
import dan200.computercraft.shared.peripheral.modem.wired.BlockWiredModemFull;
import dan200.computercraft.shared.peripheral.modem.wired.ItemCable;
import dan200.computercraft.shared.peripheral.modem.wireless.BlockAdvancedModem;
import dan200.computercraft.shared.peripheral.modem.wireless.ItemAdvancedModem;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.peripheral.printer.TilePrinter;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.proxy.ICCTurtleProxy;
import dan200.computercraft.shared.proxy.IComputerCraftProxy;
import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.items.ItemTurtleAdvanced;
import dan200.computercraft.shared.turtle.items.ItemTurtleLegacy;
import dan200.computercraft.shared.turtle.items.ItemTurtleNormal;
@@ -68,6 +76,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
@@ -86,20 +95,27 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@Mod(
modid = ComputerCraft.MOD_ID, name = ComputerCraft.NAME, version = ComputerCraft.VERSION,
modid = ComputerCraft.MOD_ID, name = "CC: Tweaked", version = "${version}",
guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory",
dependencies = "required:forge@[14.23.4.2746,)"
)
public class ComputerCraft
{
public static final String MOD_ID = "computercraft";
static final String VERSION = "${version}";
static final String NAME = "CC: Tweaked";
// GUI IDs
public static final int diskDriveGUIID = 100;
public static final int computerGUIID = 101;
public static final int printerGUIID = 102;
public static final int turtleGUIID = 103;
// ComputerCraftEdu uses ID 104
public static final int printoutGUIID = 105;
public static final int pocketComputerGUIID = 106;
public static final int viewComputerGUIID = 110;
// Configuration options
public static final String[] DEFAULT_HTTP_WHITELIST = new String[] { "*" };
@@ -117,11 +133,8 @@ public class ComputerCraft
public static boolean disable_lua51_features = false;
public static String default_computer_settings = "";
public static boolean debug_enable = true;
public static boolean logPeripheralErrors = false;
public static int computer_threads = 1;
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
public static long maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos( 5 );
public static boolean logPeripheralErrors = false;
public static boolean http_enable = true;
public static boolean http_websocket_enable = true;
@@ -159,7 +172,7 @@ public class ComputerCraft
public static final int terminalHeight_pocketComputer = 20;
// Blocks and Items
public static final class Blocks
public static class Blocks
{
public static BlockComputer computer;
public static BlockCommandComputer commandComputer;
@@ -174,7 +187,7 @@ public class ComputerCraft
public static BlockWiredModemFull wiredModemFull;
}
public static final class Items
public static class Items
{
public static ItemComputer computer;
public static ItemCommandComputer commandComputer;
@@ -197,7 +210,7 @@ public class ComputerCraft
public static ItemBlock wiredModemFull;
}
public static final class TurtleUpgrades
public static class TurtleUpgrades
{
public static TurtleModem wirelessModem;
public static TurtleModem advancedModem;
@@ -211,7 +224,7 @@ public class ComputerCraft
public static TurtleHoe diamondHoe;
}
public static final class PocketUpgrades
public static class PocketUpgrades
{
public static PocketModem wirelessModem;
public static PocketModem advancedModem;
@@ -222,7 +235,7 @@ public class ComputerCraft
}
@Deprecated
public static final class Upgrades
public static class Upgrades
{
public static TurtleModem advancedModem;
}
@@ -241,14 +254,20 @@ public class ComputerCraft
public static List<IPeripheralProvider> peripheralProviders = new ArrayList<>();
// Implementation
@Mod.Instance( ComputerCraft.MOD_ID )
@Mod.Instance( value = ComputerCraft.MOD_ID )
public static ComputerCraft instance;
@SidedProxy(
clientSide = "dan200.computercraft.client.proxy.ComputerCraftProxyClient",
serverSide = "dan200.computercraft.shared.proxy.ComputerCraftProxyCommon"
)
private static ComputerCraftProxyCommon proxy;
private static IComputerCraftProxy proxy;
@SidedProxy(
clientSide = "dan200.computercraft.client.proxy.CCTurtleProxyClient",
serverSide = "dan200.computercraft.shared.proxy.CCTurtleProxyCommon"
)
private static ICCTurtleProxy turtleProxy;
@Mod.EventHandler
public void preInit( FMLPreInitializationEvent event )
@@ -258,19 +277,24 @@ public class ComputerCraft
// Load config
Config.load( event.getSuggestedConfigurationFile() );
// Setup network
NetworkHandler.setup();
proxy.preInit();
turtleProxy.preInit();
}
@Mod.EventHandler
public void init( FMLInitializationEvent event )
{
proxy.init();
turtleProxy.init();
}
@Mod.EventHandler
public void onServerStarting( FMLServerStartingEvent event )
{
ComputerCraftProxyCommon.initServer( event.getServer() );
proxy.initServer( event.getServer() );
}
@Mod.EventHandler
@@ -280,7 +304,6 @@ public class ComputerCraft
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
MainThread.reset();
Tracking.reset();
}
}
@@ -292,14 +315,65 @@ public class ComputerCraft
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
MainThread.reset();
Tracking.reset();
}
}
public static String getVersion()
{
return VERSION;
return "${version}";
}
public static void openDiskDriveGUI( EntityPlayer player, TileDiskDrive drive )
{
BlockPos pos = drive.getPos();
player.openGui( ComputerCraft.instance, ComputerCraft.diskDriveGUIID, player.getEntityWorld(), pos.getX(), pos.getY(), pos.getZ() );
}
public static void openComputerGUI( EntityPlayer player, TileComputer computer )
{
BlockPos pos = computer.getPos();
player.openGui( ComputerCraft.instance, ComputerCraft.computerGUIID, player.getEntityWorld(), pos.getX(), pos.getY(), pos.getZ() );
}
public static void openPrinterGUI( EntityPlayer player, TilePrinter printer )
{
BlockPos pos = printer.getPos();
player.openGui( ComputerCraft.instance, ComputerCraft.printerGUIID, player.getEntityWorld(), pos.getX(), pos.getY(), pos.getZ() );
}
public static void openTurtleGUI( EntityPlayer player, TileTurtle turtle )
{
BlockPos pos = turtle.getPos();
player.openGui( instance, ComputerCraft.turtleGUIID, player.getEntityWorld(), pos.getX(), pos.getY(), pos.getZ() );
}
public static void openPrintoutGUI( EntityPlayer player, EnumHand hand )
{
player.openGui( ComputerCraft.instance, ComputerCraft.printoutGUIID, player.getEntityWorld(), hand.ordinal(), 0, 0 );
}
public static void openPocketComputerGUI( EntityPlayer player, EnumHand hand )
{
player.openGui( ComputerCraft.instance, ComputerCraft.pocketComputerGUIID, player.getEntityWorld(), hand.ordinal(), 0, 0 );
}
public static void openComputerGUI( EntityPlayer player, ServerComputer computer )
{
ComputerFamily family = computer.getFamily();
int width = 0, height = 0;
Terminal terminal = computer.getTerminal();
if( terminal != null )
{
width = terminal.getWidth();
height = terminal.getHeight();
}
// Pack useful terminal information into the various coordinate bits.
// These are extracted in ComputerCraftProxyCommon.getClientGuiElement
player.openGui( ComputerCraft.instance, ComputerCraft.viewComputerGUIID, player.getEntityWorld(),
computer.getInstanceID(), family.ordinal(), (width & 0xFFFF) << 16 | (height & 0xFFFF)
);
}
private static File getBaseDir()
@@ -312,6 +386,16 @@ public class ComputerCraft
return new File( getBaseDir(), "resourcepacks" );
}
public static boolean canPlayerUseCommands( EntityPlayer player )
{
MinecraftServer server = player.getServer();
if( server != null )
{
return server.getPlayerList().canSendCommands( player.getGameProfile() );
}
return false;
}
@Deprecated
public static void registerPermissionProvider( ITurtlePermissionProvider provider )
{
@@ -449,9 +533,6 @@ public class ComputerCraft
if( subResource.exists() ) mounts.add( new FileMount( subResource, 0 ) );
}
}
catch( FileNotFoundException ignored )
{
}
catch( IOException | RuntimeException e )
{
ComputerCraft.log.error( "Could not load resource pack '" + resourcePackName + "'", e );
@@ -561,7 +642,7 @@ public class ComputerCraft
private static File getContainingJar( Class<?> modClass )
{
String path = modClass.getProtectionDomain().getCodeSource().getLocation().getPath();
int bangIndex = path.indexOf( '!' );
int bangIndex = path.indexOf( "!" );
if( bangIndex >= 0 )
{
path = path.substring( 0, bangIndex );
@@ -592,7 +673,7 @@ public class ComputerCraft
private static File getDebugCodeDir( Class<?> modClass )
{
String path = modClass.getProtectionDomain().getCodeSource().getLocation().getPath();
int bangIndex = path.indexOf( '!' );
int bangIndex = path.indexOf( "!" );
return bangIndex >= 0 ? null : new File( new File( path ).getParentFile(), "../.." );
}
@@ -649,12 +730,5 @@ public class ComputerCraft
{
return Peripherals.getPeripheral( world, pos, side );
}
@Deprecated
public static boolean canPlayerUseCommands( EntityPlayer player )
{
MinecraftServer server = player.getServer();
return server != null && server.getPlayerList().canSendCommands( player.getGameProfile() );
}
//endregion
}

View File

@@ -173,8 +173,8 @@ public final class ComputerCraftAPI
* Registers a peripheral provider to convert blocks into {@link IPeripheral} implementations.
*
* @param provider The peripheral provider to register.
* @see IPeripheral
* @see IPeripheralProvider
* @see dan200.computercraft.api.peripheral.IPeripheral
* @see dan200.computercraft.api.peripheral.IPeripheralProvider
*/
public static void registerPeripheralProvider( @Nonnull IPeripheralProvider provider )
{
@@ -198,7 +198,7 @@ public final class ComputerCraftAPI
* this during the load() method of your mod.
*
* @param upgrade The turtle upgrade to register.
* @see ITurtleUpgrade
* @see dan200.computercraft.api.turtle.ITurtleUpgrade
*/
public static void registerTurtleUpgrade( @Nonnull ITurtleUpgrade upgrade )
{
@@ -223,7 +223,7 @@ public final class ComputerCraftAPI
* Registers a bundled redstone provider to provide bundled redstone output for blocks.
*
* @param provider The bundled redstone provider to register.
* @see IBundledRedstoneProvider
* @see dan200.computercraft.api.redstone.IBundledRedstoneProvider
*/
public static void registerBundledRedstoneProvider( @Nonnull IBundledRedstoneProvider provider )
{
@@ -249,7 +249,7 @@ public final class ComputerCraftAPI
* @param side The side to extract the bundled redstone output from.
* @return If there is a block capable of emitting bundled redstone at the location, it's signal (0-65535) will be returned.
* If there is no block capable of emitting bundled redstone at the location, -1 will be returned.
* @see IBundledRedstoneProvider
* @see dan200.computercraft.api.redstone.IBundledRedstoneProvider
*/
public static int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull EnumFacing side )
{
@@ -272,7 +272,7 @@ public final class ComputerCraftAPI
* Registers a media provider to provide {@link IMedia} implementations for Items
*
* @param provider The media provider to register.
* @see IMediaProvider
* @see dan200.computercraft.api.media.IMediaProvider
*/
public static void registerMediaProvider( @Nonnull IMediaProvider provider )
{
@@ -294,7 +294,7 @@ public final class ComputerCraftAPI
* Registers a permission provider to restrict where turtles can move or build.
*
* @param provider The turtle permission provider to register.
* @see ITurtlePermissionProvider
* @see dan200.computercraft.api.permissions.ITurtlePermissionProvider
* @deprecated Prefer using {@link dan200.computercraft.api.turtle.event.TurtleBlockEvent} or the standard Forge events.
*/
@Deprecated
@@ -481,7 +481,7 @@ public final class ComputerCraftAPI
}
catch( Exception e )
{
System.err.println( "ComputerCraftAPI: ComputerCraft not found." );
System.out.println( "ComputerCraftAPI: ComputerCraft not found." );
}
finally
{
@@ -498,7 +498,7 @@ public final class ComputerCraftAPI
}
catch( NoSuchMethodException e )
{
System.err.println( "ComputerCraftAPI: ComputerCraft method " + name + " not found." );
System.out.println( "ComputerCraftAPI: ComputerCraft method " + name + " not found." );
return null;
}
}

View File

@@ -17,7 +17,6 @@ import javax.annotation.Nullable;
* @see ILuaAPI
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
*/
@FunctionalInterface
public interface ILuaAPIFactory
{
/**

View File

@@ -32,12 +32,7 @@ public interface ILuaContext
* intercepted, or the computer will leak memory and end up in a broken state.
*/
@Nonnull
default Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[0].equals( "terminate" ) ) throw new LuaException( "Terminated", 0 );
return results;
}
Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException;
/**
* The same as {@link #pullEvent(String)}, except "terminated" events are ignored. Only use this if you want to
@@ -52,10 +47,7 @@ public interface ILuaContext
* @see #pullEvent(String)
*/
@Nonnull
default Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException
{
return yield( new Object[] { filter } );
}
Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException;
/**
* Yield the current coroutine with some arguments until it is resumed. This method is exactly equivalent to

View File

@@ -7,7 +7,6 @@
package dan200.computercraft.api.media;
import dan200.computercraft.api.filesystem.IMount;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.SoundEvent;
import net.minecraft.world.World;
@@ -17,9 +16,7 @@ import javax.annotation.Nullable;
/**
* Represents an item that can be placed in a disk drive and used by a Computer.
*
* Implement this interface on your {@link Item} class to allow it to be used in the drive. Alternatively, register
* a {@link IMediaProvider}.
* Implement this interface on your Item class to allow it to be used in the drive.
*/
public interface IMedia
{
@@ -46,7 +43,7 @@ public interface IMedia
/**
* If this disk represents an item with audio (like a record), get the readable name of the audio track. ie:
* "Jonathan Coulton - Still Alive"
* "Jonathon Coulton - Still Alive"
*
* @param stack The {@link ItemStack} to modify.
* @return The name, or null if this item does not represent an item with audio.
@@ -77,7 +74,7 @@ public interface IMedia
* @param world The world in which the item and disk drive reside.
* @return The mount, or null if this item does not represent an item with data. If the mount returned also
* implements {@link dan200.computercraft.api.filesystem.IWritableMount}, it will mounted using mountWritable()
* @see IMount
* @see dan200.computercraft.api.filesystem.IMount
* @see dan200.computercraft.api.filesystem.IWritableMount
* @see dan200.computercraft.api.ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see dan200.computercraft.api.ComputerCraftAPI#createResourceMount(Class, String, String)

View File

@@ -23,7 +23,7 @@ public interface IMediaProvider
* Produce an IMedia implementation from an ItemStack.
*
* @param stack The stack from which to extract the media information.
* @return An {@link IMedia} implementation, or {@code null} if the item is not something you wish to handle
* @return An IMedia implementation, or null if the item is not something you wish to handle
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(IMediaProvider)
*/
@Nullable

View File

@@ -9,8 +9,6 @@ package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaTask;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
@@ -148,7 +146,7 @@ public interface IComputerAccess
*
* You may supply {@code null} to indicate that no arguments are to be supplied.
* @throws RuntimeException If the peripheral has been detached.
* @see IPeripheral#callMethod
* @see dan200.computercraft.api.peripheral.IPeripheral#callMethod
*/
void queueEvent( @Nonnull String event, @Nullable Object[] arguments );
@@ -181,8 +179,8 @@ public interface IComputerAccess
}
/**
* Get a reachable peripheral with the given attachment name. This is a equivalent to
* {@link #getAvailablePeripherals()}{@code .get(name)}, though may be more efficient.
* Get a reachable peripheral with the given attachement name. This is a equivalent to
* {@link #getAvailablePeripherals()}{@code .get(name)}, though may be more performant.
*
* @param name The peripheral's attached name
* @return The reachable peripheral, or {@code null} if none can be found.
@@ -193,23 +191,4 @@ public interface IComputerAccess
{
return null;
}
/**
* Get a {@link IWorkMonitor} for tasks your peripheral might execute on the main (server) thread.
*
* This should be used to ensure your peripheral integrates with ComputerCraft's monitoring and limiting of how much
* server time each computer consumes. You should not need to use this if you use
* {@link ILuaContext#issueMainThreadTask(ILuaTask)} - this is intended for mods with their own system for running
* work on the main thread.
*
* Please note that the returned implementation is <em>not</em> thread-safe, and should only be used from the main
* thread.
*
* @return The work monitor for the main thread, or {@code null} if this computer does not have one.
*/
@Nullable
default IWorkMonitor getMainThreadMonitor()
{
return null;
}
}

View File

@@ -41,8 +41,8 @@ public interface IPeripheral
* This is called when a lua program on an attached computer calls {@code peripheral.call()} with
* one of the methods exposed by {@link #getMethodNames()}.
*
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe when interacting
* with Minecraft objects.
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe
* when interacting with Minecraft objects.
*
* @param computer The interface to the computer that is making the call. Remember that multiple
* computers can be attached to a peripheral at once.
@@ -75,21 +75,20 @@ public interface IPeripheral
Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
/**
* Is called when when a computer is attaching to the peripheral.
* Is called when canAttachToSide has returned true, and a computer is attaching to the peripheral.
*
* This will occur when a peripheral is placed next to an active computer, when a computer is turned on next to a
* peripheral, when a turtle travels into a square next to a peripheral, or when a wired modem adjacent to this
* peripheral is does any of the above.
* peripheral, or when a turtle travels into a square next to a peripheral.
*
* Between calls to attach and {@link #detach}, the attached computer can make method calls on the peripheral using
* Between calls to attach() and detach(), the attached computer can make method calls on the peripheral using
* {@code peripheral.call()}. This method can be used to keep track of which computers are attached to the
* peripheral, or to take action when attachment occurs.
*
* Be aware that will be called from both the server thread and ComputerCraft Lua thread, and so must be thread-safe
* and reentrant.
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe
* when interacting with Minecraft objects.
*
* @param computer The interface to the computer that is being attached. Remember that multiple computers can be
* attached to a peripheral at once.
* @param computer The interface to the computer that is being attached. Remember that multiple
* computers can be attached to a peripheral at once.
* @see #detach
*/
default void attach( @Nonnull IComputerAccess computer )
@@ -97,21 +96,19 @@ public interface IPeripheral
}
/**
* Called when a computer is detaching from the peripheral.
* Is called when a computer is detaching from the peripheral.
*
* This will occur when a computer shuts down, when the peripheral is removed while attached to computers, when a
* turtle moves away from a block attached to a peripheral, or when a wired modem adjacent to this peripheral is
* detached.
* This will occur when a computer shuts down, when the peripheral is removed while attached to computers,
* or when a turtle moves away from a square attached to a peripheral. This method can be used to keep track of
* which computers are attached to the peripheral, or to take action when detachment
* occurs.
*
* This method can be used to keep track of which computers are attached to the peripheral, or to take action when
* detachment occurs.
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe
* when interacting with Minecraft objects.
*
* Be aware that this will be called from both the server and ComputerCraft Lua thread, and must be thread-safe
* and reentrant.
*
* @param computer The interface to the computer that is being detached. Remember that multiple computers can be
* attached to a peripheral at once.
* @see #attach
* @param computer The interface to the computer that is being detached. Remember that multiple
* computers can be attached to a peripheral at once.
* @see #detach
*/
default void detach( @Nonnull IComputerAccess computer )
{

View File

@@ -6,7 +6,6 @@
package dan200.computercraft.api.peripheral;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
@@ -17,8 +16,6 @@ import javax.annotation.Nullable;
/**
* This interface is used to create peripheral implementations for blocks.
*
* If you have a {@link TileEntity} which acts as a peripheral, you may alternatively implement {@link IPeripheralTile}.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)
*/
@FunctionalInterface

View File

@@ -1,32 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.peripheral;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A {@link net.minecraft.tileentity.TileEntity} which may act as a peripheral.
*
* If you need more complex capabilities (such as handling TEs not belonging to your mod), you should use
* {@link IPeripheralProvider}.
*/
public interface IPeripheralTile
{
/**
* Get the peripheral on the given {@code side}.
*
* @param side The side to get the peripheral from.
* @return A peripheral, or {@code null} if there is not a peripheral here.
* @see IPeripheralProvider#getPeripheral(World, BlockPos, EnumFacing)
*/
@Nullable
IPeripheral getPeripheral( @Nonnull EnumFacing side );
}

View File

@@ -1,78 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.peripheral;
import javax.annotation.Nonnull;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* Monitors "work" associated with a computer, keeping track of how much a computer has done, and ensuring every
* computer receives a fair share of any processing time.
*
* This is primarily intended for work done by peripherals on the main thread (such as on a tile entity's tick), but
* could be used for other purposes (such as complex computations done on another thread).
*
* Before running a task, one should call {@link #canWork()} to determine if the computer is currently allowed to
* execute work. If that returns true, you should execute the task and use {@link #trackWork(long, TimeUnit)} to inform
* the monitor how long that task took.
*
* Alternatively, use {@link #runWork(Runnable)} to run and keep track of work.
*
* @see IComputerAccess#getMainThreadMonitor()
*/
public interface IWorkMonitor
{
/**
* If the owning computer is currently allowed to execute work.
*
* @return If we can execute work right now.
*/
boolean canWork();
/**
* If the owning computer is currently allowed to execute work, and has ample time to do so.
*
* This is effectively a more restrictive form of {@link #canWork()}. One should use that in order to determine if
* you may do an initial piece of work, and shouldWork to determine if any additional task may be performed.
*
* @return If we should execute work right now.
*/
boolean shouldWork();
/**
* Inform the monitor how long some piece of work took to execute.
*
* @param time The time some task took to run
* @param unit The unit that {@code time} was measured in.
*/
void trackWork( long time, @Nonnull TimeUnit unit );
/**
* Run a task if possible, and inform the monitor of how long it took.
*
* @param runnable The task to run.
* @return If the task was actually run (namely, {@link #canWork()} returned {@code true}).
*/
default boolean runWork( @Nonnull Runnable runnable )
{
Objects.requireNonNull( runnable, "runnable should not be null" );
if( !canWork() ) return false;
long start = System.nanoTime();
try
{
runnable.run();
}
finally
{
trackWork( System.nanoTime() - start, TimeUnit.NANOSECONDS );
}
return true;
}
}

View File

@@ -24,22 +24,10 @@ public interface IPocketAccess
* Gets the entity holding this item.
*
* @return The holding entity. This may be {@code null}.
* @deprecated Use {@link #getValidEntity()} where possible.
*/
@Nullable
@Deprecated
Entity getEntity();
/**
* Gets the entity holding this item with additional safety checks.
*
* This must be called on the server thread.
*
* @return The holding entity, or {@code null} if none exists.
*/
@Nullable
Entity getValidEntity();
/**
* Get the colour of this pocket computer as a RGB number.
*

View File

@@ -19,7 +19,7 @@ import javax.annotation.Nullable;
/**
* Additional peripherals for pocket computers.
*
* This is similar to {@link ITurtleUpgrade}.
* This is similar to {@link dan200.computercraft.api.turtle.ITurtleUpgrade}.
*/
public interface IPocketUpgrade
{
@@ -54,9 +54,6 @@ public interface IPocketUpgrade
* pocket computer which holds this upgrade. This item stack is also used to determine the upgrade given by
* {@code pocket.equip()}/{@code pocket.unequip()}.
*
* Ideally this should be constant over a session. It is recommended that you cache
* the item too, in order to prevent constructing it every time the method is called.
*
* @return The item stack used for crafting. This can be {@link ItemStack#EMPTY} if crafting is disabled.
*/
@Nonnull

View File

@@ -79,9 +79,6 @@ public interface ITurtleUpgrade
* with to create a turtle which holds this upgrade. This item stack is also used
* to determine the upgrade given by {@code turtle.equip()}
*
* Ideally this should be constant over a session. It is recommended that you cache
* the item too, in order to prevent constructing it every time the method is called.
*
* @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
*/
@Nonnull

View File

@@ -19,6 +19,7 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.common.eventhandler.Cancelable;
import javax.annotation.Nonnull;
import java.util.Map;
@@ -36,6 +37,7 @@ import java.util.Objects;
* Be aware that some events (such as {@link TurtleInventoryEvent}) do not necessarily interact
* with a block, simply objects within that block space.
*/
@Cancelable
public abstract class TurtleBlockEvent extends TurtlePlayerEvent
{
private final World world;
@@ -82,6 +84,7 @@ public abstract class TurtleBlockEvent extends TurtlePlayerEvent
*
* @see TurtleAction#DIG
*/
@Cancelable
public static class Dig extends TurtleBlockEvent
{
private final IBlockState block;
@@ -139,6 +142,7 @@ public abstract class TurtleBlockEvent extends TurtlePlayerEvent
*
* @see TurtleAction#MOVE
*/
@Cancelable
public static class Move extends TurtleBlockEvent
{
public Move( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos )
@@ -152,6 +156,7 @@ public abstract class TurtleBlockEvent extends TurtlePlayerEvent
*
* @see TurtleAction#PLACE
*/
@Cancelable
public static class Place extends TurtleBlockEvent
{
private final ItemStack stack;
@@ -183,6 +188,7 @@ public abstract class TurtleBlockEvent extends TurtlePlayerEvent
*
* @see TurtleAction#INSPECT
*/
@Cancelable
public static class Inspect extends TurtleBlockEvent
{
private final IBlockState state;
@@ -223,7 +229,7 @@ public abstract class TurtleBlockEvent extends TurtlePlayerEvent
/**
* Add new information to the inspection result. Note this will override fields with the same name.
*
* @param newData The data to add. Note all values should be convertible to Lua (see
* @param newData The data to add. Note all values should be convertable to Lua (see
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
*/
public void addData( @Nonnull Map<String, ?> newData )

View File

@@ -1,74 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.Objects;
/**
* Fired when a turtle gathers data on an item in its inventory.
*
* You may prevent items being inspected, or add additional information to the result. Be aware that this is fired on
* the computer thread, and so any operations on it must be thread safe.
*
* @see TurtleAction#INSPECT_ITEM
*/
public class TurtleInspectItemEvent extends TurtleActionEvent
{
private final ItemStack stack;
private final Map<String, Object> data;
public TurtleInspectItemEvent( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack stack, @Nonnull Map<String, Object> data )
{
super( turtle, TurtleAction.INSPECT_ITEM );
Objects.requireNonNull( stack, "stack cannot be null" );
Objects.requireNonNull( data, "data cannot be null" );
this.stack = stack;
this.data = data;
}
/**
* The item which is currently being inspected.
*
* @return The item stack which is being inspected. This should <b>not</b> be modified.
*/
@Nonnull
public ItemStack getStack()
{
return stack;
}
/**
* Get the "inspection data" from this item, which will be returned to the user.
*
* @return This items's inspection data.
*/
@Nonnull
public Map<String, Object> getData()
{
return data;
}
/**
* Add new information to the inspection result. Note this will override fields with the same name.
*
* @param newData The data to add. Note all values should be convertible to Lua (see
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
*/
public void addData( @Nonnull Map<String, ?> newData )
{
Objects.requireNonNull( newData, "newData cannot be null" );
data.putAll( newData );
}
}

View File

@@ -11,6 +11,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.fml.common.eventhandler.Cancelable;
import net.minecraftforge.items.IItemHandler;
import javax.annotation.Nonnull;
@@ -20,6 +21,7 @@ import java.util.Objects;
/**
* Fired when a turtle attempts to interact with an inventory.
*/
@Cancelable
public abstract class TurtleInventoryEvent extends TurtleBlockEvent
{
private final IItemHandler handler;
@@ -46,6 +48,7 @@ public abstract class TurtleInventoryEvent extends TurtleBlockEvent
*
* @see TurtleAction#SUCK
*/
@Cancelable
public static class Suck extends TurtleInventoryEvent
{
public Suck( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler )
@@ -59,6 +62,7 @@ public abstract class TurtleInventoryEvent extends TurtleBlockEvent
*
* @see TurtleAction#DROP
*/
@Cancelable
public static class Drop extends TurtleInventoryEvent
{
private final ItemStack stack;
@@ -74,12 +78,13 @@ public abstract class TurtleInventoryEvent extends TurtleBlockEvent
/**
* The item which will be inserted into the inventory/dropped on the ground.
*
* @return The item stack which will be dropped. This should <b>not</b> be modified.
* Note that this is a copy of the original stack, and so should not be modified, as that will have no effect.
*
* @return The item stack which will be dropped.
*/
@Nonnull
public ItemStack getStack()
{
return stack;
return stack.copy();
}
}
}

View File

@@ -1,91 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
/**
* Fired when a turtle attempts to refuel from an item.
*
* One may use {@link #setCanceled(boolean, String)} to prevent refueling from this specific item. Additionally, you
* may use {@link #setHandler(Handler)} to register a custom fuel provider.
*/
public class TurtleRefuelEvent extends TurtleActionEvent
{
private final ItemStack stack;
private Handler handler;
public TurtleRefuelEvent( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack stack )
{
super( turtle, TurtleAction.REFUEL );
Objects.requireNonNull( turtle, "turtle cannot be null" );
this.stack = stack;
}
/**
* Get the stack we are attempting to refuel from.
*
* Do not modify the returned stack - all modifications should be done within the {@link Handler}.
*
* @return The stack to refuel from.
*/
public ItemStack getStack()
{
return stack;
}
/**
* Get the refuel handler for this stack.
*
* @return The refuel handler, or {@code null} if none has currently been set.
* @see #setHandler(Handler)
*/
@Nullable
public Handler getHandler()
{
return handler;
}
/**
* Set the refuel handler for this stack.
*
* You should call this if you can actually refuel from this item, and ideally only if there are no existing
* handlers.
*
* @param handler The new refuel handler.
* @see #getHandler()
*/
public void setHandler( @Nullable Handler handler )
{
this.handler = handler;
}
/**
* Handles refuelling a turtle from a specific item.
*/
@FunctionalInterface
public interface Handler
{
/**
* Refuel a turtle using an item.
*
* @param turtle The turtle to refuel.
* @param stack The stack to refuel with.
* @param slot The slot the stack resides within. This may be used to modify the inventory afterwards.
* @param limit The maximum number of refuel operations to perform. This will often correspond to the number of
* items to consume.
* @return The amount of fuel gained.
*/
int refuel( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack stack, int slot, int limit );
}
}

View File

@@ -9,7 +9,6 @@ package dan200.computercraft.client;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.render.TurtleModelLoader;
import dan200.computercraft.shared.media.items.ItemDiskLegacy;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.turtle.items.ItemTurtleBase;
import dan200.computercraft.shared.util.Colour;
import net.minecraft.client.Minecraft;
@@ -39,9 +38,9 @@ import javax.annotation.Nonnull;
* Registers textures and models for items.
*/
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Side.CLIENT )
public final class ClientRegistry
public class ClientRegistry
{
private static final String[] EXTRA_MODELS = new String[] {
private static final String[] EXTRA_MODELS = {
"turtle_modem_off_left",
"turtle_modem_on_left",
"turtle_modem_off_right",
@@ -59,8 +58,6 @@ public final class ClientRegistry
"turtle_elf_overlay",
};
private ClientRegistry() {}
@SubscribeEvent
public static void registerModels( ModelRegistryEvent event )
{
@@ -120,7 +117,7 @@ public final class ClientRegistry
public static void onItemColours( ColorHandlerEvent.Item event )
{
event.getItemColors().registerItemColorHandler(
( stack, layer ) -> layer == 1 ? ((ItemDiskLegacy) stack.getItem()).getColour( stack ) : 0xFFFFFF,
( stack, layer ) -> layer == 0 ? 0xFFFFFF : ((ItemDiskLegacy) stack.getItem()).getColour( stack ),
ComputerCraft.Items.disk, ComputerCraft.Items.diskExpanded
);
@@ -130,21 +127,32 @@ public final class ClientRegistry
case 0:
default:
return 0xFFFFFF;
case 1: // Frame colour
return ComputerCraft.Items.pocketComputer.getColour( stack );
case 2: // Light colour
case 1:
{
int light = ItemPocketComputer.getLightState( stack );
return light == -1 ? Colour.Black.getHex() : light;
// Frame colour
int colour = ComputerCraft.Items.pocketComputer.getColour( stack );
return colour == -1 ? 0xFFFFFF : colour;
}
case 2:
{
// Light colour
int colour = ComputerCraft.Items.pocketComputer.getLightState( stack );
return colour == -1 ? Colour.Black.getHex() : colour;
}
}
}, ComputerCraft.Items.pocketComputer );
// Setup turtle colours
event.getItemColors().registerItemColorHandler(
( stack, tintIndex ) -> tintIndex == 0 ? ((ItemTurtleBase) stack.getItem()).getColour( stack ) : 0xFFFFFF,
ComputerCraft.Blocks.turtle, ComputerCraft.Blocks.turtleExpanded, ComputerCraft.Blocks.turtleAdvanced
);
event.getItemColors().registerItemColorHandler( ( stack, tintIndex ) -> {
if( tintIndex == 0 )
{
ItemTurtleBase turtle = (ItemTurtleBase) stack.getItem();
int colour = turtle.getColour( stack );
if( colour != -1 ) return colour;
}
return 0xFFFFFF;
}, ComputerCraft.Blocks.turtle, ComputerCraft.Blocks.turtleExpanded, ComputerCraft.Blocks.turtleAdvanced );
}
private static void registerItemModel( Item item, int damage, String name )

View File

@@ -13,14 +13,12 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiNewChat;
import net.minecraft.client.gui.GuiUtilRenderComponents;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextFormatting;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nullable;
import java.util.List;
public class ClientTableFormatter implements TableFormatter
{
@@ -28,7 +26,7 @@ public class ClientTableFormatter implements TableFormatter
private static Int2IntOpenHashMap lastHeights = new Int2IntOpenHashMap();
private static FontRenderer renderer()
private FontRenderer renderer()
{
return Minecraft.getMinecraft().fontRenderer;
}
@@ -64,13 +62,7 @@ public class ClientTableFormatter implements TableFormatter
@Override
public void writeLine( int id, ITextComponent component )
{
Minecraft mc = Minecraft.getMinecraft();
GuiNewChat chat = mc.ingameGUI.getChatGUI();
// Trim the text if it goes over the allowed length
int maxWidth = MathHelper.floor( chat.getChatWidth() / chat.getChatScale() );
List<ITextComponent> list = GuiUtilRenderComponents.splitText( component, maxWidth, mc.fontRenderer, false, false );
if( !list.isEmpty() ) chat.printChatMessageWithOptionalDeletion( list.get( 0 ), id );
Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessageWithOptionalDeletion( component, id );
}
@Override

View File

@@ -19,7 +19,7 @@ import org.lwjgl.opengl.GL11;
import java.util.Arrays;
public final class FixedWidthFontRenderer
public class FixedWidthFontRenderer
{
private static final ResourceLocation FONT = new ResourceLocation( "computercraft", "textures/gui/term_font.png" );
public static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/term_background.png" );
@@ -93,7 +93,7 @@ public final class FixedWidthFontRenderer
private boolean isGreyScale( int colour )
{
return colour == 0 || colour == 15 || colour == 7 || colour == 8;
return (colour == 0 || colour == 15 || colour == 7 || colour == 8);
}
public void drawStringBackgroundPart( int x, int y, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p )

View File

@@ -24,10 +24,9 @@ import java.io.IOException;
public class GuiComputer extends GuiContainer
{
public static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners.png" );
public static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners_advanced.png" );
public static final ResourceLocation BACKGROUND_COMMAND = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners_command.png" );
public static final ResourceLocation BACKGROUND_COLOUR = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners_colour.png" );
private static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/corners.png" );
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation( "computercraft", "textures/gui/corners_advanced.png" );
private static final ResourceLocation BACKGROUND_COMMAND = new ResourceLocation( "computercraft", "textures/gui/corners_command.png" );
private final ComputerFamily m_family;
private final ClientComputer m_computer;
@@ -81,6 +80,12 @@ public class GuiComputer extends GuiContainer
Keyboard.enableRepeatEvents( false );
}
@Override
public boolean doesGuiPauseGame()
{
return false;
}
@Override
public void updateScreen()
{
@@ -139,7 +144,7 @@ public class GuiComputer extends GuiContainer
}
@Override
public void drawScreen( int mouseX, int mouseY, float partialTicks )
public void drawScreen( int mouseX, int mouseY, float f )
{
// Work out where to draw
int startX = (width - m_terminal.getWidth()) / 2;
@@ -151,7 +156,7 @@ public class GuiComputer extends GuiContainer
drawDefaultBackground();
// Draw terminal
m_terminal.draw( mc, startX, startY, mouseX, mouseY );
m_terminal.draw( this.mc, startX, startY, mouseX, mouseY );
// Draw a border around the terminal
GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f );
@@ -159,23 +164,29 @@ public class GuiComputer extends GuiContainer
{
case Normal:
default:
mc.getTextureManager().bindTexture( BACKGROUND_NORMAL );
{
this.mc.getTextureManager().bindTexture( BACKGROUND );
break;
}
case Advanced:
mc.getTextureManager().bindTexture( BACKGROUND_ADVANCED );
{
this.mc.getTextureManager().bindTexture( BACKGROUND_ADVANCED );
break;
}
case Command:
mc.getTextureManager().bindTexture( BACKGROUND_COMMAND );
{
this.mc.getTextureManager().bindTexture( BACKGROUND_COMMAND );
break;
}
}
drawTexturedModalRect( startX - 12, startY - 12, 12, 28, 12, 12 );
drawTexturedModalRect( startX - 12, endY, 12, 40, 12, 12 );
drawTexturedModalRect( startX - 12, endY, 12, 40, 12, 16 );
drawTexturedModalRect( endX, startY - 12, 24, 28, 12, 12 );
drawTexturedModalRect( endX, endY, 24, 40, 12, 12 );
drawTexturedModalRect( endX, endY, 24, 40, 12, 16 );
drawTexturedModalRect( startX, startY - 12, 0, 0, endX - startX, 12 );
drawTexturedModalRect( startX, endY, 0, 12, endX - startX, 12 );
drawTexturedModalRect( startX, endY, 0, 12, endX - startX, 16 );
drawTexturedModalRect( startX - 12, startY, 0, 28, 12, endY - startY );
drawTexturedModalRect( endX, startY, 36, 28, 12, endY - startY );

View File

@@ -22,8 +22,10 @@ public class GuiConfigCC extends GuiConfig
super( parentScreen, Config.getConfigElements(), ComputerCraft.MOD_ID, false, false, "CC: Tweaked" );
}
public static class Factory implements IModGuiFactory
public static class Factory
implements IModGuiFactory
{
@Override
public void initialize( Minecraft minecraft )
{

View File

@@ -25,19 +25,21 @@ public class GuiDiskDrive extends GuiContainer
}
@Override
protected void drawGuiContainerForegroundLayer( int mouseX, int mouseY )
protected void drawGuiContainerForegroundLayer( int par1, int par2 )
{
String title = m_container.getDiskDrive().getDisplayName().getUnformattedText();
fontRenderer.drawString( title, (xSize - fontRenderer.getStringWidth( title )) / 2, 6, 0x404040 );
fontRenderer.drawString( I18n.format( "container.inventory" ), 8, ySize - 96 + 2, 0x404040 );
fontRenderer.drawString( I18n.format( "container.inventory" ), 8, (ySize - 96) + 2, 0x404040 );
}
@Override
protected void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
protected void drawGuiContainerBackgroundLayer( float f, int i, int j )
{
GlStateManager.color( 1.0F, 1.0F, 1.0F, 1.0F );
mc.getTextureManager().bindTexture( BACKGROUND );
drawTexturedModalRect( guiLeft, guiTop, 0, 0, xSize, ySize );
this.mc.getTextureManager().bindTexture( BACKGROUND );
int l = (width - xSize) / 2;
int i1 = (height - ySize) / 2;
drawTexturedModalRect( l, i1, 0, 0, xSize, ySize );
}
@Override

View File

@@ -8,7 +8,6 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.media.inventory.ContainerHeldItem;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
public class GuiPocketComputer extends GuiComputer
{
@@ -17,7 +16,7 @@ public class GuiPocketComputer extends GuiComputer
super(
container,
ComputerCraft.Items.pocketComputer.getFamily( container.getStack() ),
ItemPocketComputer.createClientComputer( container.getStack() ),
ComputerCraft.Items.pocketComputer.createClientComputer( container.getStack() ),
ComputerCraft.terminalWidth_pocketComputer,
ComputerCraft.terminalHeight_pocketComputer
);

View File

@@ -16,30 +16,36 @@ public class GuiPrinter extends GuiContainer
{
private static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/printer.png" );
private final ContainerPrinter container;
private final ContainerPrinter m_container;
public GuiPrinter( ContainerPrinter container )
{
super( container );
this.container = container;
m_container = container;
}
@Override
protected void drawGuiContainerForegroundLayer( int mouseX, int mouseY )
protected void drawGuiContainerForegroundLayer( int par1, int par2 )
{
String title = container.getPrinter().getDisplayName().getUnformattedText();
String title = m_container.getPrinter().getDisplayName().getUnformattedText();
fontRenderer.drawString( title, (xSize - fontRenderer.getStringWidth( title )) / 2, 6, 0x404040 );
fontRenderer.drawString( I18n.format( "container.inventory" ), 8, ySize - 96 + 2, 0x404040 );
fontRenderer.drawString( I18n.format( "container.inventory" ), 8, (ySize - 96) + 2, 0x404040 );
}
@Override
protected void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
protected void drawGuiContainerBackgroundLayer( float f, int i, int j )
{
GlStateManager.color( 1.0F, 1.0F, 1.0F, 1.0F );
mc.getTextureManager().bindTexture( BACKGROUND );
drawTexturedModalRect( guiLeft, guiTop, 0, 0, xSize, ySize );
this.mc.getTextureManager().bindTexture( BACKGROUND );
int startX = (width - xSize) / 2;
int startY = (height - ySize) / 2;
drawTexturedModalRect( startX, startY, 0, 0, xSize, ySize );
if( container.isPrinting() ) drawTexturedModalRect( guiLeft + 34, guiTop + 21, 176, 0, 25, 45 );
boolean printing = m_container.isPrinting();
if( printing )
{
drawTexturedModalRect( startX + 34, startY + 21, 176, 0, 25, 45 );
}
}
@Override

View File

@@ -29,8 +29,6 @@ public class GuiPrintout extends GuiContainer
{
super( container );
ySize = Y_SIZE;
String[] text = ItemPrintout.getText( container.getStack() );
m_text = new TextBuffer[text.length];
for( int i = 0; i < m_text.length; i++ ) m_text[i] = new TextBuffer( text[i] );
@@ -44,6 +42,12 @@ public class GuiPrintout extends GuiContainer
m_book = ItemPrintout.getType( container.getStack() ) == ItemPrintout.Type.Book;
}
@Override
public boolean doesGuiPauseGame()
{
return false;
}
@Override
protected void keyTyped( char c, int k ) throws IOException
{
@@ -69,35 +73,41 @@ public class GuiPrintout extends GuiContainer
int mouseWheelChange = Mouse.getEventDWheel();
if( mouseWheelChange < 0 )
{
// Scroll up goes to the next page
// Up
if( m_page < m_pages - 1 ) m_page++;
}
else if( mouseWheelChange > 0 )
{
// Scroll down goes to the previous page
// Down
if( m_page > 0 ) m_page--;
}
}
@Override
protected void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
protected void drawGuiContainerForegroundLayer( int par1, int par2 )
{
// Draw the printout
GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f );
drawBorder( guiLeft, guiTop, zLevel, m_page, m_pages, m_book );
drawText( guiLeft + X_TEXT_MARGIN, guiTop + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * m_page, m_text, m_colours );
}
@Override
public void drawScreen( int mouseX, int mouseY, float partialTicks )
protected void drawGuiContainerBackgroundLayer( float var1, int var2, int var3 )
{
// We must take the background further back in order to not overlap with our printed pages.
zLevel--;
drawDefaultBackground();
zLevel++;
}
super.drawScreen( mouseX, mouseY, partialTicks );
renderHoveredToolTip( mouseX, mouseY );
@Override
public void drawScreen( int mouseX, int mouseY, float f )
{
// Draw background
zLevel = zLevel - 1;
drawDefaultBackground();
zLevel = zLevel + 1;
// Draw the printout
GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f );
int startY = (height - Y_SIZE) / 2;
int startX = (width - X_SIZE) / 2;
drawBorder( startX, startY, zLevel, m_page, m_pages, m_book );
drawText( startX + X_TEXT_MARGIN, startY + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * m_page, m_text, m_colours );
}
}

View File

@@ -23,7 +23,7 @@ import java.io.IOException;
public class GuiTurtle extends GuiContainer
{
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation( "computercraft", "textures/gui/turtle.png" );
private static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/turtle.png" );
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation( "computercraft", "textures/gui/turtle_advanced.png" );
private ContainerTurtle m_container;
@@ -50,8 +50,8 @@ public class GuiTurtle extends GuiContainer
super.initGui();
Keyboard.enableRepeatEvents( true );
m_terminalGui = new WidgetTerminal(
guiLeft + 8,
guiTop + 8,
(width - xSize) / 2 + 8,
(height - ySize) / 2 + 8,
ComputerCraft.terminalWidth_turtle,
ComputerCraft.terminalHeight_turtle,
() -> m_computer,
@@ -98,8 +98,8 @@ public class GuiTurtle extends GuiContainer
public void handleMouseInput() throws IOException
{
super.handleMouseInput();
int x = Mouse.getEventX() * width / mc.displayWidth;
int y = height - Mouse.getEventY() * height / mc.displayHeight - 1;
int x = Mouse.getEventX() * this.width / mc.displayWidth;
int y = this.height - Mouse.getEventY() * this.height / mc.displayHeight - 1;
m_terminalGui.handleMouseInput( x, y );
}
@@ -112,29 +112,34 @@ public class GuiTurtle extends GuiContainer
protected void drawSelectionSlot( boolean advanced )
{
int x = (width - xSize) / 2;
int y = (height - ySize) / 2;
// Draw selection slot
int slot = m_container.getSelectedSlot();
if( slot >= 0 )
{
GlStateManager.color( 1.0F, 1.0F, 1.0F, 1.0F );
int slotX = slot % 4;
int slotY = slot / 4;
mc.getTextureManager().bindTexture( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
drawTexturedModalRect( guiLeft + m_container.m_turtleInvStartX - 2 + slotX * 18, guiTop + m_container.m_playerInvStartY - 2 + slotY * 18, 0, 217, 24, 24 );
int slotX = (slot % 4);
int slotY = (slot / 4);
this.mc.getTextureManager().bindTexture( advanced ? BACKGROUND_ADVANCED : BACKGROUND );
drawTexturedModalRect( x + m_container.m_turtleInvStartX - 2 + slotX * 18, y + m_container.m_playerInvStartY - 2 + slotY * 18, 0, 217, 24, 24 );
}
}
@Override
protected void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
protected void drawGuiContainerBackgroundLayer( float f, int mouseX, int mouseY )
{
// Draw term
boolean advanced = m_family == ComputerFamily.Advanced;
boolean advanced = (m_family == ComputerFamily.Advanced);
m_terminalGui.draw( Minecraft.getMinecraft(), 0, 0, mouseX, mouseY );
// Draw border/inventory
GlStateManager.color( 1.0F, 1.0F, 1.0F, 1.0F );
mc.getTextureManager().bindTexture( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
drawTexturedModalRect( guiLeft, guiTop, 0, 0, xSize, ySize );
this.mc.getTextureManager().bindTexture( advanced ? BACKGROUND_ADVANCED : BACKGROUND );
int x = (width - xSize) / 2;
int y = (height - ySize) / 2;
drawTexturedModalRect( x, y, 0, 0, xSize, ySize );
drawSelectionSlot( advanced );
}

View File

@@ -31,23 +31,23 @@ public class WidgetTerminal extends Widget
private final IComputerContainer m_computer;
private float m_terminateTimer = 0.0f;
private float m_rebootTimer = 0.0f;
private float m_shutdownTimer = 0.0f;
private float m_terminateTimer;
private float m_rebootTimer;
private float m_shutdownTimer;
private int m_lastClickButton = -1;
private int m_lastClickX = -1;
private int m_lastClickY = -1;
private int m_lastClickButton;
private int m_lastClickX;
private int m_lastClickY;
private boolean m_focus = false;
private boolean m_allowFocusLoss = true;
private boolean m_focus;
private boolean m_allowFocusLoss;
private int m_leftMargin;
private int m_rightMargin;
private int m_topMargin;
private int m_bottomMargin;
private final ArrayList<Integer> m_keysDown = new ArrayList<>();
private ArrayList<Integer> m_keysDown;
public WidgetTerminal( int x, int y, int termWidth, int termHeight, IComputerContainer computer, int leftMargin, int rightMargin, int topMargin, int bottomMargin )
{
@@ -58,11 +58,23 @@ public class WidgetTerminal extends Widget
);
m_computer = computer;
m_terminateTimer = 0.0f;
m_rebootTimer = 0.0f;
m_shutdownTimer = 0.0f;
m_lastClickButton = -1;
m_lastClickX = -1;
m_lastClickY = -1;
m_focus = false;
m_allowFocusLoss = true;
m_leftMargin = leftMargin;
m_rightMargin = rightMargin;
m_topMargin = topMargin;
m_bottomMargin = bottomMargin;
m_keysDown = new ArrayList<>();
}
public void setAllowFocusLoss( boolean allowFocusLoss )
@@ -82,9 +94,9 @@ public class WidgetTerminal extends Widget
String clipboard = GuiScreen.getClipboardString();
if( clipboard != null )
{
// Clip to the first occurrence of \r or \n
int newLineIndex1 = clipboard.indexOf( '\r' );
int newLineIndex2 = clipboard.indexOf( '\n' );
// Clip to the first occurance of \r or \n
int newLineIndex1 = clipboard.indexOf( "\r" );
int newLineIndex2 = clipboard.indexOf( "\n" );
if( newLineIndex1 >= 0 && newLineIndex2 >= 0 )
{
clipboard = clipboard.substring( 0, Math.min( newLineIndex1, newLineIndex2 ) );
@@ -110,7 +122,9 @@ public class WidgetTerminal extends Widget
}
// Queue the "paste" event
queueEvent( "paste", new Object[] { clipboard } );
queueEvent( "paste", new Object[] {
clipboard
} );
}
}
return true;
@@ -129,15 +143,18 @@ public class WidgetTerminal extends Widget
}
// Queue the "key" event
IComputer computer = m_computer.getComputer();
if( computer != null ) computer.keyDown( key, repeat );
queueEvent( "key", new Object[] {
key, repeat
} );
handled = true;
}
if( (ch >= 32 && ch <= 126) || (ch >= 160 && ch <= 255) ) // printable chars in byte range
{
// Queue the "char" event
queueEvent( "char", new Object[] { Character.toString( ch ) } );
queueEvent( "char", new Object[] {
Character.toString( ch )
} );
handled = true;
}
@@ -172,7 +189,9 @@ public class WidgetTerminal extends Widget
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
computer.mouseClick( button + 1, charX + 1, charY + 1 );
computer.queueEvent( "mouse_click", new Object[] {
button + 1, charX + 1, charY + 1
} );
m_lastClickButton = button;
m_lastClickX = charX;
@@ -203,8 +222,9 @@ public class WidgetTerminal extends Widget
if( m_focus )
{
// Queue the "key_up" event
IComputer computer = m_computer.getComputer();
if( computer != null ) computer.keyUp( key );
queueEvent( "key_up", new Object[] {
key
} );
handled = true;
}
}
@@ -231,7 +251,12 @@ public class WidgetTerminal extends Widget
if( m_lastClickButton >= 0 && !Mouse.isButtonDown( m_lastClickButton ) )
{
if( m_focus ) computer.mouseUp( m_lastClickButton + 1, charX + 1, charY + 1 );
if( m_focus )
{
computer.queueEvent( "mouse_up", new Object[] {
m_lastClickButton + 1, charX + 1, charY + 1
} );
}
m_lastClickButton = -1;
}
@@ -245,16 +270,22 @@ public class WidgetTerminal extends Widget
{
if( wheelChange < 0 )
{
computer.mouseScroll( 1, charX + 1, charY + 1 );
computer.queueEvent( "mouse_scroll", new Object[] {
1, charX + 1, charY + 1
} );
}
else if( wheelChange > 0 )
{
computer.mouseScroll( -1, charX + 1, charY + 1 );
computer.queueEvent( "mouse_scroll", new Object[] {
-1, charX + 1, charY + 1
} );
}
if( m_lastClickButton >= 0 && (charX != m_lastClickX || charY != m_lastClickY) )
{
computer.mouseDrag( m_lastClickButton + 1, charX + 1, charY + 1 );
computer.queueEvent( "mouse_drag", new Object[] {
m_lastClickButton + 1, charX + 1, charY + 1
} );
m_lastClickX = charX;
m_lastClickY = charY;
}
@@ -274,8 +305,11 @@ public class WidgetTerminal extends Widget
{
if( m_terminateTimer < TERMINATE_TIME )
{
m_terminateTimer += 0.05f;
if( m_terminateTimer >= TERMINATE_TIME ) queueEvent( "terminate" );
m_terminateTimer = m_terminateTimer + 0.05f;
if( m_terminateTimer >= TERMINATE_TIME )
{
queueEvent( "terminate" );
}
}
}
else
@@ -288,11 +322,14 @@ public class WidgetTerminal extends Widget
{
if( m_rebootTimer < TERMINATE_TIME )
{
m_rebootTimer += 0.05f;
m_rebootTimer = m_rebootTimer + 0.05f;
if( m_rebootTimer >= TERMINATE_TIME )
{
IComputer computer = m_computer.getComputer();
if( computer != null ) computer.reboot();
if( computer != null )
{
computer.reboot();
}
}
}
}
@@ -306,11 +343,14 @@ public class WidgetTerminal extends Widget
{
if( m_shutdownTimer < TERMINATE_TIME )
{
m_shutdownTimer += 0.05f;
m_shutdownTimer = m_shutdownTimer + 0.05f;
if( m_shutdownTimer >= TERMINATE_TIME )
{
IComputer computer = m_computer.getComputer();
if( computer != null ) computer.shutdown();
if( computer != null )
{
computer.shutdown();
}
}
}
}
@@ -337,7 +377,7 @@ public class WidgetTerminal extends Widget
{
// Draw the screen contents
IComputer computer = m_computer.getComputer();
Terminal terminal = computer != null ? computer.getTerminal() : null;
Terminal terminal = (computer != null) ? computer.getTerminal() : null;
if( terminal != null )
{
// Draw the terminal
@@ -415,12 +455,18 @@ public class WidgetTerminal extends Widget
private void queueEvent( String event )
{
IComputer computer = m_computer.getComputer();
if( computer != null ) computer.queueEvent( event );
if( computer != null )
{
computer.queueEvent( event );
}
}
private void queueEvent( String event, Object[] args )
{
IComputer computer = m_computer.getComputer();
if( computer != null ) computer.queueEvent( event, args );
if( computer != null )
{
computer.queueEvent( event, args );
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.proxy;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.shared.proxy.CCTurtleProxyCommon;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import net.minecraftforge.fml.client.registry.ClientRegistry;
public class CCTurtleProxyClient extends CCTurtleProxyCommon
{
@Override
public void init()
{
super.init();
// Setup renderers
ClientRegistry.bindTileEntitySpecialRenderer( TileTurtle.class, new TileEntityTurtleRenderer() );
}
}

View File

@@ -9,13 +9,11 @@ package dan200.computercraft.client.proxy;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.render.TileEntityCableRenderer;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.shared.command.CommandCopy;
import dan200.computercraft.shared.peripheral.modem.wired.TileCable;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.client.registry.ClientRegistry;
@@ -42,11 +40,10 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
// Setup renderers
ClientRegistry.bindTileEntitySpecialRenderer( TileMonitor.class, new TileEntityMonitorRenderer() );
ClientRegistry.bindTileEntitySpecialRenderer( TileCable.class, new TileEntityCableRenderer() );
ClientRegistry.bindTileEntitySpecialRenderer( TileTurtle.class, new TileEntityTurtleRenderer() );
}
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Side.CLIENT )
public static final class ForgeHandlers
public static class ForgeHandlers
{
@SubscribeEvent
public static void onWorldUnload( WorldEvent.Unload event )

View File

@@ -12,24 +12,25 @@ import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.RenderItem;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.item.ItemStack;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.event.RenderSpecificHandEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.opengl.GL11;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
import static dan200.computercraft.client.gui.GuiComputer.*;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
/**
* Emulates map rendering for pocket computers
@@ -37,10 +38,6 @@ import static dan200.computercraft.client.gui.GuiComputer.*;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Side.CLIENT )
public final class ItemPocketRenderer extends ItemMapLikeRenderer
{
private static final int MARGIN = 2;
private static final int FRAME = 12;
private static final int LIGHT_HEIGHT = 8;
private static final ItemPocketRenderer INSTANCE = new ItemPocketRenderer();
private ItemPocketRenderer()
@@ -60,194 +57,116 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
@Override
protected void renderItem( ItemStack stack )
{
ClientComputer computer = ItemPocketComputer.createClientComputer( stack );
Terminal terminal = computer == null ? null : computer.getTerminal();
int termWidth, termHeight;
if( terminal == null )
{
termWidth = ComputerCraft.terminalWidth_pocketComputer;
termHeight = ComputerCraft.terminalHeight_pocketComputer;
}
else
{
termWidth = terminal.getWidth();
termHeight = terminal.getHeight();
}
int width = termWidth * FONT_WIDTH + MARGIN * 2;
int height = termHeight * FONT_HEIGHT + MARGIN * 2;
// Setup various transformations. Note that these are partially adapted from the corresponding method
// Setup various transformations. Note that these are partially adapated from the corresponding method
// in ItemRenderer
GlStateManager.pushMatrix();
GlStateManager.disableLighting();
GlStateManager.disableDepth();
GlStateManager.rotate( 180f, 0f, 1f, 0f );
GlStateManager.rotate( 180f, 0f, 0f, 1f );
GlStateManager.scale( 0.5, 0.5, 0.5 );
double scale = 0.75 / Math.max( width + FRAME * 2, height + FRAME * 2 + LIGHT_HEIGHT );
GlStateManager.scale( scale, scale, 0 );
GlStateManager.translate( -0.5 * width, -0.5 * height, 0 );
ItemPocketComputer pocketComputer = ComputerCraft.Items.pocketComputer;
ClientComputer computer = pocketComputer.createClientComputer( stack );
// Render the main frame
ComputerFamily family = ComputerCraft.Items.pocketComputer.getFamily( stack );
int frameColour = ComputerCraft.Items.pocketComputer.getColour( stack );
renderFrame( family, frameColour, width, height );
// Render the light
int lightColour = ItemPocketComputer.getLightState( stack );
if( lightColour == -1 ) lightColour = Colour.Black.getHex();
renderLight( lightColour, width, height );
if( computer != null && terminal != null )
{
// If we've a computer and terminal then attempt to render it.
renderTerminal( terminal, !computer.isColour(), width, height );
}
else
{
// Otherwise render a plain background
Minecraft.getMinecraft().getTextureManager().bindTexture( BACKGROUND );
// First render the background item. We use the item's model rather than a direct texture as this ensures
// we display the pocket light and other such decorations.
GlStateManager.pushMatrix();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.scale( 1.0f, -1.0f, 1.0f );
Colour black = Colour.Black;
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION );
renderTexture( buffer, 0, 0, 0, 0, width, height, black.getR(), black.getG(), black.getB() );
tessellator.draw();
Minecraft minecraft = Minecraft.getMinecraft();
TextureManager textureManager = minecraft.getTextureManager();
RenderItem renderItem = minecraft.getRenderItem();
// Copy of RenderItem#renderItemModelIntoGUI but without the translation or scaling
textureManager.bindTexture( TextureMap.LOCATION_BLOCKS_TEXTURE );
textureManager.getTexture( TextureMap.LOCATION_BLOCKS_TEXTURE ).setBlurMipmap( false, false );
GlStateManager.enableRescaleNormal();
GlStateManager.enableAlpha();
GlStateManager.alphaFunc( GL11.GL_GREATER, 0.1F );
GlStateManager.enableBlend();
GlStateManager.blendFunc( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA );
GlStateManager.color( 1.0F, 1.0F, 1.0F, 1.0F );
IBakedModel bakedmodel = renderItem.getItemModelWithOverrides( stack, null, null );
bakedmodel = ForgeHooksClient.handleCameraTransforms( bakedmodel, ItemCameraTransforms.TransformType.GUI, false );
renderItem.renderItem( stack, bakedmodel );
GlStateManager.disableAlpha();
GlStateManager.disableRescaleNormal();
GlStateManager.popMatrix();
}
// If we've a computer and terminal then attempt to render it.
if( computer != null )
{
Terminal terminal = computer.getTerminal();
if( terminal != null )
{
synchronized( terminal )
{
GlStateManager.pushMatrix();
GlStateManager.disableDepth();
// Reset the position to be at the top left corner of the pocket computer
// Note we translate towards the screen slightly too.
GlStateManager.translate( -8 / 16.0, -8 / 16.0, 0.5 / 16.0 );
// Translate to the top left of the screen.
GlStateManager.translate( 4 / 16.0, 3 / 16.0, 0 );
// Work out the scaling required to resize the terminal in order to fit on the computer
final int margin = 2;
int tw = terminal.getWidth();
int th = terminal.getHeight();
int width = tw * FONT_WIDTH + margin * 2;
int height = th * FONT_HEIGHT + margin * 2;
int max = Math.max( height, width );
// The grid is 8 * 8 wide, so we start with a base of 1/2 (8 / 16).
double scale = 1.0 / 2.0 / max;
GlStateManager.scale( scale, scale, scale );
// The margin/start positions are determined in order for the terminal to be centred.
int startX = (max - width) / 2 + margin;
int startY = (max - height) / 2 + margin;
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
boolean greyscale = !computer.isColour();
Palette palette = terminal.getPalette();
// Render the actual text
for( int line = 0; line < th; line++ )
{
TextBuffer text = terminal.getLine( line );
TextBuffer colour = terminal.getTextColourLine( line );
TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
fontRenderer.drawString(
text, startX, startY + line * FONT_HEIGHT,
colour, backgroundColour, margin, margin, greyscale, palette
);
}
// And render the cursor;
int tx = terminal.getCursorX(), ty = terminal.getCursorY();
if( terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink() &&
tx >= 0 && ty >= 0 && tx < tw && ty < th )
{
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
fontRenderer.drawString(
new TextBuffer( '_', 1 ), startX + FONT_WIDTH * tx, startY + FONT_HEIGHT * ty,
cursorColour, null, 0, 0, greyscale, palette
);
}
GlStateManager.enableDepth();
GlStateManager.popMatrix();
}
}
}
GlStateManager.enableDepth();
GlStateManager.enableLighting();
GlStateManager.popMatrix();
}
private static void renderFrame( ComputerFamily family, int colour, int width, int height )
{
Minecraft.getMinecraft().getTextureManager().bindTexture( colour != -1
? BACKGROUND_COLOUR
: family == ComputerFamily.Normal ? BACKGROUND_NORMAL : BACKGROUND_ADVANCED
);
float r = ((colour >>> 16) & 0xFF) / 255.0f;
float g = ((colour >>> 8) & 0xFF) / 255.0f;
float b = (colour & 0xFF) / 255.0f;
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR );
// Top left, middle, right
renderTexture( buffer, -FRAME, -FRAME, 12, 28, FRAME, FRAME, r, g, b );
renderTexture( buffer, 0, -FRAME, 0, 0, width, FRAME, r, g, b );
renderTexture( buffer, width, -FRAME, 24, 28, FRAME, FRAME, r, g, b );
// Left and bright border
renderTexture( buffer, -FRAME, 0, 0, 28, FRAME, height, r, g, b );
renderTexture( buffer, width, 0, 36, 28, FRAME, height, r, g, b );
// Bottom left, middle, right. We do this in three portions: the top inner corners, an extended region for
// lights, and then the bottom outer corners.
renderTexture( buffer, -FRAME, height, 12, 40, FRAME, FRAME / 2, r, g, b );
renderTexture( buffer, 0, height, 0, 12, width, FRAME / 2, r, g, b );
renderTexture( buffer, width, height, 24, 40, FRAME, FRAME / 2, r, g, b );
renderTexture( buffer, -FRAME, height + FRAME / 2, 12, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( buffer, 0, height + FRAME / 2, 0, 16, width, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( buffer, width, height + FRAME / 2, 24, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( buffer, -FRAME, height + LIGHT_HEIGHT + FRAME / 2, 12, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
renderTexture( buffer, 0, height + LIGHT_HEIGHT + FRAME / 2, 0, 12 + FRAME / 2, width, FRAME / 2, r, g, b );
renderTexture( buffer, width, height + LIGHT_HEIGHT + FRAME / 2, 24, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
tessellator.draw();
}
private static void renderLight( int colour, int width, int height )
{
GlStateManager.enableBlend();
GlStateManager.disableTexture2D();
float r = ((colour >>> 16) & 0xFF) / 255.0f;
float g = ((colour >>> 8) & 0xFF) / 255.0f;
float b = (colour & 0xFF) / 255.0f;
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR );
buffer.pos( width - LIGHT_HEIGHT * 2, height + LIGHT_HEIGHT + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( width, height + LIGHT_HEIGHT + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( width, height + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( width - LIGHT_HEIGHT * 2, height + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
tessellator.draw();
GlStateManager.enableTexture2D();
}
private static void renderTerminal( Terminal terminal, boolean greyscale, int width, int height )
{
synchronized( terminal )
{
int termWidth = terminal.getWidth();
int termHeight = terminal.getHeight();
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
Palette palette = terminal.getPalette();
// Render top/bottom borders
TextBuffer emptyLine = new TextBuffer( ' ', termWidth );
fontRenderer.drawString(
emptyLine, MARGIN, 0,
terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ), MARGIN, MARGIN, greyscale, palette
);
fontRenderer.drawString(
emptyLine, MARGIN, 2 * MARGIN + (termHeight - 1) * FixedWidthFontRenderer.FONT_HEIGHT,
terminal.getTextColourLine( termHeight - 1 ), terminal.getBackgroundColourLine( termHeight - 1 ), MARGIN, MARGIN, greyscale, palette
);
// Render the actual text
for( int line = 0; line < termWidth; line++ )
{
TextBuffer text = terminal.getLine( line );
TextBuffer colour = terminal.getTextColourLine( line );
TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
fontRenderer.drawString(
text, MARGIN, MARGIN + line * FONT_HEIGHT,
colour, backgroundColour, MARGIN, MARGIN, greyscale, palette
);
}
// And render the cursor;
int tx = terminal.getCursorX(), ty = terminal.getCursorY();
if( terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink() &&
tx >= 0 && ty >= 0 && tx < termWidth && ty < termHeight )
{
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
fontRenderer.drawString(
new TextBuffer( '_', 1 ), MARGIN + FONT_WIDTH * tx, MARGIN + FONT_HEIGHT * ty,
cursorColour, null, 0, 0, greyscale, palette
);
}
}
}
private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, float r, float g, float b )
{
renderTexture( builder, x, y, textureX, textureY, width, height, width, height, r, g, b );
}
private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, int textureWidth, int textureHeight, float r, float g, float b )
{
float scale = 1 / 255.0f;
builder.pos( x, y + height, 0 ).tex( textureX * scale, (textureY + textureHeight) * scale ).color( r, g, b, 1.0f ).endVertex();
builder.pos( x + width, y + height, 0 ).tex( (textureX + textureWidth) * scale, (textureY + textureHeight) * scale ).color( r, g, b, 1.0f ).endVertex();
builder.pos( x + width, y, 0 ).tex( (textureX + textureWidth) * scale, textureY * scale ).color( r, g, b, 1.0f ).endVertex();
builder.pos( x, y, 0 ).tex( textureX * scale, textureY * scale ).color( r, g, b, 1.0f ).endVertex();
}
}

View File

@@ -23,7 +23,7 @@ import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAG
import static dan200.computercraft.shared.media.items.ItemPrintout.LINE_MAX_LENGTH;
/**
* Emulates map and item-frame rendering for printouts
* Emulates map and item-frame rendering for prinouts
*/
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Side.CLIENT )
public final class ItemPrintoutRenderer extends ItemMapLikeRenderer

View File

@@ -180,7 +180,7 @@ public final class ModelTransformer
*
* This also provides the ability to swap vertices through {@link #swap(int, int)} to allow reordering.
*/
private static final class BakedQuadBuilder implements IVertexConsumer
private static class BakedQuadBuilder implements IVertexConsumer
{
private final VertexFormat format;
@@ -195,7 +195,7 @@ public final class ModelTransformer
private BakedQuadBuilder( VertexFormat format )
{
this.format = format;
vertexData = new int[format.getSize()];
this.vertexData = new int[format.getSize()];
}
@Nonnull
@@ -208,7 +208,7 @@ public final class ModelTransformer
@Override
public void setQuadTint( int tint )
{
quadTint = tint;
this.quadTint = tint;
}
@Override

View File

@@ -1,118 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.client.event.DrawBlockHighlightEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.opengl.GL11;
import java.util.EnumSet;
import static net.minecraft.util.EnumFacing.*;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Side.CLIENT )
public final class MonitorHighlightRenderer
{
private static final float EXPAND = 0.002f;
private MonitorHighlightRenderer()
{
}
@SubscribeEvent
public static void drawHighlight( DrawBlockHighlightEvent event )
{
if( event.getTarget().typeOfHit != RayTraceResult.Type.BLOCK || event.getPlayer().isSneaking() ) return;
World world = event.getPlayer().getEntityWorld();
BlockPos pos = event.getTarget().getBlockPos();
if( world.getBlockState( pos ).getBlock() != ComputerCraft.Blocks.peripheral ) return;
TileEntity tile = world.getTileEntity( pos );
if( !(tile instanceof TileMonitor) ) return;
TileMonitor monitor = (TileMonitor) tile;
event.setCanceled( true );
// Determine which sides are part of the external faces of the monitor, and so which need to be rendered.
EnumSet<EnumFacing> faces = EnumSet.allOf( EnumFacing.class );
EnumFacing front = monitor.getFront();
faces.remove( front );
if( monitor.getXIndex() != 0 ) faces.remove( monitor.getRight().getOpposite() );
if( monitor.getXIndex() != monitor.getWidth() - 1 ) faces.remove( monitor.getRight() );
if( monitor.getYIndex() != 0 ) faces.remove( monitor.getDown().getOpposite() );
if( monitor.getYIndex() != monitor.getHeight() - 1 ) faces.remove( monitor.getDown() );
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
GL11.glLineWidth( 2.0F );
GlStateManager.disableTexture2D();
GlStateManager.depthMask( false );
GlStateManager.pushMatrix();
EntityPlayer player = event.getPlayer();
double x = player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks();
double y = player.lastTickPosY + (player.posY - player.lastTickPosY) * event.getPartialTicks();
double z = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * event.getPartialTicks();
GlStateManager.translate( -x + pos.getX(), -y + pos.getY(), -z + pos.getZ() );
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION_COLOR );
// I wish I could think of a better way to do this
if( faces.contains( NORTH ) || faces.contains( WEST ) ) line( buffer, 0, 0, 0, UP );
if( faces.contains( SOUTH ) || faces.contains( WEST ) ) line( buffer, 0, 0, 1, UP );
if( faces.contains( NORTH ) || faces.contains( EAST ) ) line( buffer, 1, 0, 0, UP );
if( faces.contains( SOUTH ) || faces.contains( EAST ) ) line( buffer, 1, 0, 1, UP );
if( faces.contains( NORTH ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 0, EAST );
if( faces.contains( SOUTH ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 1, EAST );
if( faces.contains( NORTH ) || faces.contains( UP ) ) line( buffer, 0, 1, 0, EAST );
if( faces.contains( SOUTH ) || faces.contains( UP ) ) line( buffer, 0, 1, 1, EAST );
if( faces.contains( WEST ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 0, SOUTH );
if( faces.contains( EAST ) || faces.contains( DOWN ) ) line( buffer, 1, 0, 0, SOUTH );
if( faces.contains( WEST ) || faces.contains( UP ) ) line( buffer, 0, 1, 0, SOUTH );
if( faces.contains( EAST ) || faces.contains( UP ) ) line( buffer, 1, 1, 0, SOUTH );
tessellator.draw();
GlStateManager.popMatrix();
GlStateManager.depthMask( true );
GlStateManager.enableTexture2D();
GlStateManager.disableBlend();
}
private static void line( BufferBuilder buffer, int x, int y, int z, EnumFacing direction )
{
double minX = x == 0 ? -EXPAND : 1 + EXPAND;
double minY = y == 0 ? -EXPAND : 1 + EXPAND;
double minZ = z == 0 ? -EXPAND : 1 + EXPAND;
buffer.pos( minX, minY, minZ ).color( 0, 0, 0, 0.4f ).endVertex();
buffer.pos(
minX + direction.getXOffset() * (1 + EXPAND * 2),
minY + direction.getYOffset() * (1 + EXPAND * 2),
minZ + direction.getZOffset() * (1 + EXPAND * 2)
).color( 0, 0, 0, 0.4f ).endVertex();
}
}

View File

@@ -12,8 +12,6 @@ import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.ResourceLocation;
@@ -22,7 +20,7 @@ import org.lwjgl.opengl.GL11;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE;
public final class PrintoutRenderer
public class PrintoutRenderer
{
private static final ResourceLocation BG = new ResourceLocation( "computercraft", "textures/gui/printout.png" );
private static final double BG_SIZE = 256.0;
@@ -60,8 +58,6 @@ public final class PrintoutRenderer
private static final int COVER_Y = Y_SIZE;
private static final int COVER_X = X_SIZE + 4 * X_FOLD_SIZE;
private PrintoutRenderer() {}
public static void drawText( int x, int y, int start, TextBuffer[] text, TextBuffer[] colours )
{
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
@@ -77,7 +73,6 @@ public final class PrintoutRenderer
GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.enableBlend();
GlStateManager.enableTexture2D();
GlStateManager.tryBlendFuncSeparate( SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO );
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
@@ -92,7 +87,6 @@ public final class PrintoutRenderer
GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.enableBlend();
GlStateManager.enableTexture2D();
GlStateManager.tryBlendFuncSeparate( SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO );
Minecraft.getMinecraft().getTextureManager().bindTexture( BG );

View File

@@ -29,13 +29,13 @@ import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.opengl.GL11;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Side.CLIENT )
public final class CableHighlightRenderer
public final class RenderOverlayCable
{
private static final float EXPAND = 0.002f;
private static final double MIN = CableBounds.MIN - EXPAND;
private static final double MAX = CableBounds.MAX + EXPAND;
private CableHighlightRenderer()
private RenderOverlayCable()
{
}
@@ -53,7 +53,7 @@ public final class CableHighlightRenderer
state = state.getActualState( world, pos );
event.setCanceled( true );
PeripheralType type = BlockCable.getPeripheralType( state );
PeripheralType type = ComputerCraft.Blocks.cable.getPeripheralType( state );
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate( GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0 );

View File

@@ -0,0 +1,188 @@
package dan200.computercraft.client.render;
import com.google.common.collect.Sets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.shared.wired.WiredNetwork;
import dan200.computercraft.shared.wired.WiredNode;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.opengl.GL11;
import java.awt.*;
import java.util.Collection;
import java.util.Set;
/**
* This is a helper to render a network when testing.
*/
public final class RenderOverlayNetwork
{
private int ticksInGame;
private IWiredElement element;
@SubscribeEvent
public void onWorldRenderLast( RenderWorldLastEvent event )
{
++ticksInGame;
RayTraceResult result = Minecraft.getMinecraft().objectMouseOver;
if( result != null && result.typeOfHit == RayTraceResult.Type.BLOCK )
{
World clientWorld = Minecraft.getMinecraft().world;
World world = FMLCommonHandler.instance().getMinecraftServerInstance().getWorld( clientWorld.provider.getDimension() );
IWiredElement newElement = ComputerCraft.getWiredElementAt( world, result.getBlockPos(), result.sideHit );
if( newElement != null ) element = newElement;
}
if( element == null ) return;
Minecraft minecraft = Minecraft.getMinecraft();
ItemStack stack = minecraft.player.getHeldItemMainhand();
ItemStack otherStack = minecraft.player.getHeldItemOffhand();
if( stack.getItem() != Items.STICK && otherStack.getItem() != Items.STICK ) return;
GlStateManager.pushMatrix();
RenderManager renderManager = minecraft.getRenderManager();
GlStateManager.translate( -renderManager.viewerPosX, -renderManager.viewerPosY, -renderManager.viewerPosZ );
GlStateManager.disableDepth();
GlStateManager.disableTexture2D();
GlStateManager.enableBlend();
GlStateManager.blendFunc( GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA );
Set<Pair<IWiredElement>> connections = Sets.newHashSet();
WiredNetwork network = (WiredNetwork) element.getNode().getNetwork();
for( WiredNode node : network.getNodes() )
{
for( WiredNode other : node.getNeighbours() )
{
connections.add( new Pair<>( node.getElement(), other.getElement() ) );
}
}
renderNetworkConnections( connections, new Color( Color.HSBtoRGB( ticksInGame % 200 / 200F, 0.6F, 1F ) ), 1f );
GlStateManager.enableDepth();
GlStateManager.enableTexture2D();
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
private void renderNetworkConnections( Collection<Pair<IWiredElement>> data, Color color, float thickness )
{
renderConnections( data, color, 1.0f, thickness );
renderConnections( data, color, 64.0f / 255.0f, thickness * 3 );
}
private void renderConnections( Collection<Pair<IWiredElement>> connections, Color color, float alpha, float thickness )
{
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
GlStateManager.pushMatrix();
GlStateManager.scale( 1, 1, 1 );
GlStateManager.color( color.getRed() / 255.0f, color.getGreen() / 255.0f, color.getBlue() / 255.0f, alpha );
GL11.glLineWidth( thickness );
renderer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION );
for( Pair<IWiredElement> connection : connections )
{
Vec3d a = connection.x.getPosition(), b = connection.y.getPosition();
renderer.pos( a.x, a.y, a.z ).endVertex();
renderer.pos( b.x, b.y, b.z ).endVertex();
}
tessellator.draw();
GlStateManager.popMatrix();
}
private void renderLabel( double x, double y, double z, String label )
{
RenderManager renderManager = Minecraft.getMinecraft().getRenderManager();
FontRenderer fontrenderer = renderManager.getFontRenderer();
if( fontrenderer == null ) return;
GlStateManager.pushMatrix();
GlStateManager.disableLighting();
float scale = 0.02666667f;
GlStateManager.translate( x, y, z );
GlStateManager.rotate( -renderManager.playerViewY, 0, 1, 0 );
GlStateManager.rotate( renderManager.playerViewX, 1, 0, 0 );
GlStateManager.scale( -scale, -scale, scale );
// Render label background
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
int width = fontrenderer.getStringWidth( label );
int xOffset = width / 2;
GlStateManager.disableTexture2D();
GlStateManager.color( 0, 0, 0, 65 / 225.0f );
renderer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION );
renderer.pos( -xOffset - 1, -1, 0 ).endVertex();
renderer.pos( -xOffset - 1, 8, 0 ).endVertex();
renderer.pos( xOffset + 1, 8, 0 ).endVertex();
renderer.pos( xOffset + 1, -1, 0 ).endVertex();
tessellator.draw();
GlStateManager.enableTexture2D();
// Render label
fontrenderer.drawString( label, -width / 2, 0, 0xFFFFFFFF );
GlStateManager.enableLighting();
GlStateManager.popMatrix();
}
private static class Pair<T>
{
public final T x;
public final T y;
public Pair( T right, T y )
{
this.x = right;
this.y = y;
}
@Override
public boolean equals( Object o )
{
if( this == o ) return true;
if( o == null || getClass() != o.getClass() ) return false;
Pair<?> p = (Pair<?>) o;
return (x.equals( p.x ) && y.equals( p.y ))
|| (x.equals( p.y ) && y.equals( p.x ));
}
@Override
public int hashCode()
{
return x.hashCode() ^ y.hashCode();
}
}
}

View File

@@ -61,11 +61,11 @@ public class TileEntityCableRenderer extends TileEntitySpecialRenderer<TileCable
state = state.getActualState( world, pos );
if( te.getPeripheralType() != PeripheralType.Cable && WorldUtil.isVecInsideInclusive( CableBounds.getModemBounds( state ), hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) )
{
state = block.getDefaultState().withProperty( BlockCable.MODEM, state.getValue( BlockCable.MODEM ) );
state = block.getDefaultState().withProperty( BlockCable.Properties.MODEM, state.getValue( BlockCable.Properties.MODEM ) );
}
else
{
state = state.withProperty( BlockCable.MODEM, BlockCableModemVariant.None );
state = state.withProperty( BlockCable.Properties.MODEM, BlockCableModemVariant.None );
}
IBakedModel model = mc.getBlockRendererDispatcher().getModelForState( state );

View File

@@ -37,7 +37,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
}
}
private static void renderMonitorAt( TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
private void renderMonitorAt( TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
{
// Render from the origin monitor
ClientMonitor originTerminal = monitor.getClientMonitor();
@@ -78,7 +78,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.rotate( pitch, 1.0f, 0.0f, 0.0f );
GlStateManager.translate(
-0.5 + TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN,
origin.getHeight() - 0.5 - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN),
(origin.getHeight() - 0.5) - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN),
0.5
);
double xSize = origin.getWidth() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);

View File

@@ -40,14 +40,14 @@ import java.util.List;
public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurtle>
{
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation( "computercraft:turtle", "inventory" );
private static final ModelResourceLocation ADVANCED_TURTLE_MODEL = new ModelResourceLocation( "computercraft:turtle_advanced", "inventory" );
private static final ModelResourceLocation ADVANCED_TURTLE_MODEL = new ModelResourceLocation( "computercraft:advanced_turtle", "inventory" );
private static final ModelResourceLocation COLOUR_TURTLE_MODEL = new ModelResourceLocation( "computercraft:turtle_white", "inventory" );
private static final ModelResourceLocation ELF_OVERLAY_MODEL = new ModelResourceLocation( "computercraft:turtle_elf_overlay", "inventory" );
@Override
public void render( TileTurtle tileEntity, double posX, double posY, double posZ, float partialTicks, int breaking, float f2 )
public void render( TileTurtle tileEntity, double posX, double posY, double posZ, float f, int i, float f2 )
{
if( tileEntity != null ) renderTurtleAt( tileEntity, posX, posY, posZ, partialTicks );
if( tileEntity != null ) renderTurtleAt( tileEntity, posX, posY, posZ, f, i );
}
public static ModelResourceLocation getTurtleModel( ComputerFamily family, boolean coloured )
@@ -78,7 +78,7 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
}
}
private void renderTurtleAt( TileTurtle turtle, double posX, double posY, double posZ, float partialTicks )
private void renderTurtleAt( TileTurtle turtle, double posX, double posY, double posZ, float f, int i )
{
// Render the label
String label = turtle.createProxy().getLabel();
@@ -93,13 +93,15 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
setLightmapDisabled( false );
}
IBlockState state = turtle.getWorld().getBlockState( turtle.getPos() );
GlStateManager.pushMatrix();
try
{
IBlockState state = turtle.getWorld().getBlockState( turtle.getPos() );
// Setup the transform
Vec3d offset = turtle.getRenderOffset( partialTicks );
float yaw = turtle.getRenderYaw( partialTicks );
Vec3d offset;
float yaw;
offset = turtle.getRenderOffset( f );
yaw = turtle.getRenderYaw( f );
GlStateManager.translate( posX + offset.x, posY + offset.y, posZ + offset.z );
// Render the turtle
@@ -113,9 +115,12 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
}
GlStateManager.translate( -0.5f, -0.5f, -0.5f );
// Render the turtle
int colour = turtle.getColour();
ComputerFamily family = turtle.getFamily();
ResourceLocation overlay = turtle.getOverlay();
int colour;
ComputerFamily family;
ResourceLocation overlay;
colour = turtle.getColour();
family = turtle.getFamily();
overlay = turtle.getOverlay();
renderModel( state, getTurtleModel( family, colour != -1 ), colour == -1 ? null : new int[] { colour } );
@@ -141,8 +146,8 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
}
// Render the upgrades
renderUpgrade( state, turtle, TurtleSide.Left, partialTicks );
renderUpgrade( state, turtle, TurtleSide.Right, partialTicks );
renderUpgrade( state, turtle, TurtleSide.Left, f );
renderUpgrade( state, turtle, TurtleSide.Right, f );
}
finally
{
@@ -151,7 +156,7 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
}
}
private static void renderUpgrade( IBlockState state, TileTurtle turtle, TurtleSide side, float f )
private void renderUpgrade( IBlockState state, TileTurtle turtle, TurtleSide side, float f )
{
ITurtleUpgrade upgrade = turtle.getUpgrade( side );
if( upgrade != null )
@@ -184,14 +189,14 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
}
}
private static void renderModel( IBlockState state, ModelResourceLocation modelLocation, int[] tints )
private void renderModel( IBlockState state, ModelResourceLocation modelLocation, int[] tints )
{
Minecraft mc = Minecraft.getMinecraft();
ModelManager modelManager = mc.getRenderItem().getItemModelMesher().getModelManager();
renderModel( state, modelManager.getModel( modelLocation ), tints );
}
private static void renderModel( IBlockState state, IBakedModel model, int[] tints )
private void renderModel( IBlockState state, IBakedModel model, int[] tints )
{
Minecraft mc = Minecraft.getMinecraft();
Tessellator tessellator = Tessellator.getInstance();
@@ -203,7 +208,7 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
}
}
private static void renderQuads( Tessellator tessellator, List<BakedQuad> quads, int[] tints )
private void renderQuads( Tessellator tessellator, List<BakedQuad> quads, int[] tints )
{
BufferBuilder buffer = tessellator.getBuffer();
VertexFormat format = DefaultVertexFormats.ITEM;

View File

@@ -21,7 +21,7 @@ import net.minecraftforge.common.model.IModelState;
import javax.annotation.Nonnull;
import java.util.function.Function;
public final class TurtleModelLoader implements ICustomModelLoader
public class TurtleModelLoader implements ICustomModelLoader
{
private static final ResourceLocation NORMAL_TURTLE_MODEL = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle" );
private static final ResourceLocation ADVANCED_TURTLE_MODEL = new ResourceLocation( ComputerCraft.MOD_ID, "block/advanced_turtle" );
@@ -64,7 +64,7 @@ public final class TurtleModelLoader implements ICustomModelLoader
throw new IllegalStateException( "Loader does not accept " + name );
}
private static final class TurtleModel implements IModel
private static class TurtleModel implements IModel
{
private final IModel family;
private final IModel colour;

View File

@@ -17,7 +17,7 @@ import net.minecraft.util.EnumFacing;
import javax.annotation.Nonnull;
import javax.vecmath.Matrix4f;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -30,8 +30,8 @@ public class TurtleMultiModel implements IBakedModel
private final Matrix4f m_leftUpgradeTransform;
private final IBakedModel m_rightUpgradeModel;
private final Matrix4f m_rightUpgradeTransform;
private List<BakedQuad> m_generalQuads = null;
private Map<EnumFacing, List<BakedQuad>> m_faceQuads = new EnumMap<>( EnumFacing.class );
private List<BakedQuad> m_generalQuads;
private Map<EnumFacing, List<BakedQuad>> m_faceQuads;
public TurtleMultiModel( IBakedModel baseModel, IBakedModel overlayModel, Matrix4f generalTransform, IBakedModel leftUpgradeModel, Matrix4f leftUpgradeTransform, IBakedModel rightUpgradeModel, Matrix4f rightUpgradeTransform )
{
@@ -43,6 +43,8 @@ public class TurtleMultiModel implements IBakedModel
m_rightUpgradeModel = rightUpgradeModel;
m_rightUpgradeTransform = rightUpgradeTransform;
m_generalTransform = generalTransform;
m_generalQuads = null;
m_faceQuads = new HashMap<>();
}
@Nonnull

View File

@@ -143,10 +143,10 @@ public class TurtleSmartItemModel implements IBakedModel
ModelResourceLocation overlayModelLocation = TileEntityTurtleRenderer.getTurtleOverlayModel( combo.m_overlay, combo.m_christmas );
IBakedModel baseModel = combo.m_colour ? colourModel : familyModel;
IBakedModel overlayModel = overlayModelLocation != null ? modelManager.getModel( overlayModelLocation ) : null;
IBakedModel overlayModel = (overlayModelLocation != null) ? modelManager.getModel( overlayModelLocation ) : null;
Matrix4f transform = combo.m_flip ? s_flip : s_identity;
Pair<IBakedModel, Matrix4f> leftModel = combo.m_leftUpgrade != null ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null;
Pair<IBakedModel, Matrix4f> rightModel = combo.m_rightUpgrade != null ? combo.m_rightUpgrade.getModel( null, TurtleSide.Right ) : null;
Pair<IBakedModel, Matrix4f> leftModel = (combo.m_leftUpgrade != null) ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null;
Pair<IBakedModel, Matrix4f> rightModel = (combo.m_rightUpgrade != null) ? combo.m_rightUpgrade.getModel( null, TurtleSide.Right ) : null;
if( leftModel != null && rightModel != null )
{
return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), rightModel.getLeft(), rightModel.getRight() );

View File

@@ -20,7 +20,7 @@ import java.util.regex.Pattern;
*/
public class AddressPredicate
{
private static final class HostRange
private static class HostRange
{
private final byte[] min;
private final byte[] max;
@@ -69,10 +69,7 @@ public class AddressPredicate
}
catch( NumberFormatException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract size of CIDR mask from '{}'.",
filter, prefixSizeStr
);
ComputerCraft.log.warn( "Cannot parse CIDR size from {} ({})", filter, prefixSizeStr );
continue;
}
@@ -83,10 +80,7 @@ public class AddressPredicate
}
catch( IllegalArgumentException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract IP address from '{}'.",
filter, prefixSizeStr
);
ComputerCraft.log.warn( "Cannot parse IP address from {} ({})", filter, addressStr );
continue;
}

View File

@@ -6,13 +6,13 @@
package dan200.computercraft.core.apis;
import com.google.common.base.Preconditions;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
public final class ApiFactories
{
@@ -25,7 +25,7 @@ public final class ApiFactories
public static void register( @Nonnull ILuaAPIFactory factory )
{
Objects.requireNonNull( factory, "provider cannot be null" );
Preconditions.checkNotNull( factory, "provider cannot be null" );
factories.add( factory );
}

View File

@@ -6,10 +6,12 @@
package dan200.computercraft.core.apis;
import com.google.common.base.Preconditions;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.IComputerOwned;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
@@ -19,22 +21,22 @@ import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public abstract class ComputerAccess implements IComputerAccess
public abstract class ComputerAccess implements IComputerAccess, IComputerOwned
{
private final IAPIEnvironment m_environment;
private final Set<String> m_mounts = new HashSet<>();
protected ComputerAccess( IAPIEnvironment environment )
protected ComputerAccess( IAPIEnvironment m_environment )
{
this.m_environment = environment;
this.m_environment = m_environment;
}
public void unmountAll()
{
FileSystem fileSystem = m_environment.getFileSystem();
for( String mount : m_mounts )
for( String m_mount : m_mounts )
{
fileSystem.unmount( mount );
fileSystem.unmount( m_mount );
}
m_mounts.clear();
}
@@ -120,15 +122,15 @@ public abstract class ComputerAccess implements IComputerAccess
@Override
public void queueEvent( @Nonnull final String event, final Object[] arguments )
{
Objects.requireNonNull( event, "event cannot be null" );
Preconditions.checkNotNull( event, "event cannot be null" );
m_environment.queueEvent( event, arguments );
}
@Nullable
@Override
public IWorkMonitor getMainThreadMonitor()
public Computer getComputer()
{
return m_environment.getMainThreadMonitor();
return m_environment.getComputer();
}
private String findFreeLocation( String desiredLoc )

View File

@@ -34,9 +34,9 @@ public class FSAPI implements ILuaAPI
private IAPIEnvironment m_env;
private FileSystem m_fileSystem;
public FSAPI( IAPIEnvironment env )
public FSAPI( IAPIEnvironment _env )
{
m_env = env;
m_env = _env;
m_fileSystem = null;
}
@@ -353,8 +353,10 @@ public class FSAPI implements ILuaAPI
return new Object[] { FileSystem.getDirectory( path ) };
}
default:
assert false;
{
assert (false);
return null;
}
}
}
}

View File

@@ -199,7 +199,9 @@ public class HTTPAPI implements ILuaAPI
}
}
default:
{
return null;
}
}
}
@@ -207,14 +209,14 @@ public class HTTPAPI implements ILuaAPI
private static HttpHeaders getHeaders( @Nonnull Map<?, ?> headerTable ) throws LuaException
{
HttpHeaders headers = new DefaultHttpHeaders();
for( Map.Entry<?, ?> entry : headerTable.entrySet() )
for( Object key : headerTable.keySet() )
{
Object value = entry.getValue();
if( entry.getKey() instanceof String && value instanceof String )
Object value = headerTable.get( key );
if( key instanceof String && value instanceof String )
{
try
{
headers.add( (String) entry.getKey(), value );
headers.add( (String) key, value );
}
catch( IllegalArgumentException e )
{

View File

@@ -7,33 +7,27 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.computer.IComputerOwned;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public interface IAPIEnvironment
public interface IAPIEnvironment extends IComputerOwned
{
@FunctionalInterface
interface IPeripheralChangeListener
{
void onPeripheralChanged( ComputerSide side, @Nullable IPeripheral newPeripheral );
void onPeripheralChanged( int side, IPeripheral newPeripheral );
}
@Override
Computer getComputer();
int getComputerID();
@Nonnull
IComputerEnvironment getComputerEnvironment();
@Nonnull
IWorkMonitor getMainThreadMonitor();
@Nonnull
Terminal getTerminal();
FileSystem getFileSystem();
@@ -44,30 +38,29 @@ public interface IAPIEnvironment
void queueEvent( String event, Object[] args );
void setOutput( ComputerSide side, int output );
void setOutput( int side, int output );
int getOutput( ComputerSide side );
int getOutput( int side );
int getInput( ComputerSide side );
int getInput( int side );
void setBundledOutput( ComputerSide side, int output );
void setBundledOutput( int side, int output );
int getBundledOutput( ComputerSide side );
int getBundledOutput( int side );
int getBundledInput( ComputerSide side );
int getBundledInput( int side );
void setPeripheralChangeListener( @Nullable IPeripheralChangeListener listener );
void setPeripheralChangeListener( IPeripheralChangeListener listener );
@Nullable
IPeripheral getPeripheral( ComputerSide side );
IPeripheral getPeripheral( int side );
String getLabel();
void setLabel( @Nullable String label );
void setLabel( String label );
void addTrackingChange( @Nonnull TrackingField field, long change );
void addTrackingChange( TrackingField field, long change );
default void addTrackingChange( @Nonnull TrackingField field )
default void addTrackingChange( TrackingField field )
{
addTrackingChange( field, 1 );
}

View File

@@ -1,281 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.TextStyle;
import java.time.temporal.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.LongUnaryOperator;
final class LuaDateTime
{
private LuaDateTime()
{
}
static void format( DateTimeFormatterBuilder formatter, String format, ZoneOffset offset ) throws LuaException
{
for( int i = 0; i < format.length(); )
{
char c;
switch( c = format.charAt( i++ ) )
{
case '\n':
formatter.appendLiteral( '\n' );
break;
default:
formatter.appendLiteral( c );
break;
case '%':
if( i >= format.length() ) break;
switch( c = format.charAt( i++ ) )
{
default:
throw new LuaException( "bad argument #1: invalid conversion specifier '%" + c + "'" );
case '%':
formatter.appendLiteral( '%' );
break;
case 'a':
formatter.appendText( ChronoField.DAY_OF_WEEK, TextStyle.SHORT );
break;
case 'A':
formatter.appendText( ChronoField.DAY_OF_WEEK, TextStyle.FULL );
break;
case 'b':
case 'h':
formatter.appendText( ChronoField.MONTH_OF_YEAR, TextStyle.SHORT );
break;
case 'B':
formatter.appendText( ChronoField.MONTH_OF_YEAR, TextStyle.FULL );
break;
case 'c':
format( formatter, "%a %b %e %H:%M:%S %Y", offset );
break;
case 'C':
formatter.appendValueReduced( CENTURY, 2, 2, 0 );
break;
case 'd':
formatter.appendValue( ChronoField.DAY_OF_MONTH, 2 );
break;
case 'D':
case 'x':
format( formatter, "%m/%d/%y", offset );
break;
case 'e':
formatter.padNext( 2 ).appendValue( ChronoField.DAY_OF_MONTH );
break;
case 'F':
format( formatter, "%Y-%m-%d", offset );
break;
case 'g':
formatter.appendValueReduced( IsoFields.WEEK_BASED_YEAR, 2, 2, 0 );
break;
case 'G':
formatter.appendValue( IsoFields.WEEK_BASED_YEAR );
break;
case 'H':
formatter.appendValue( ChronoField.HOUR_OF_DAY, 2 );
break;
case 'I':
formatter.appendValue( ChronoField.HOUR_OF_AMPM );
break;
case 'j':
formatter.appendValue( ChronoField.DAY_OF_YEAR, 3 );
break;
case 'm':
formatter.appendValue( ChronoField.MONTH_OF_YEAR, 2 );
break;
case 'M':
formatter.appendValue( ChronoField.MINUTE_OF_HOUR, 2 );
break;
case 'n':
formatter.appendLiteral( '\n' );
break;
case 'p':
formatter.appendText( ChronoField.AMPM_OF_DAY );
break;
case 'r':
format( formatter, "%I:%M:%S %p", offset );
break;
case 'R':
format( formatter, "%H:%M", offset );
break;
case 'S':
formatter.appendValue( ChronoField.SECOND_OF_MINUTE, 2 );
break;
case 't':
formatter.appendLiteral( '\t' );
break;
case 'T':
case 'X':
format( formatter, "%H:%M:%S", offset );
break;
case 'u':
formatter.appendValue( ChronoField.DAY_OF_WEEK );
break;
case 'U':
formatter.appendValue( ChronoField.ALIGNED_WEEK_OF_YEAR, 2 );
break;
case 'V':
formatter.appendValue( IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2 );
break;
case 'w':
formatter.appendValue( ZERO_WEEK );
break;
case 'W':
formatter.appendValue( WeekFields.ISO.weekOfYear(), 2 );
break;
case 'y':
formatter.appendValueReduced( ChronoField.YEAR, 2, 2, 0 );
break;
case 'Y':
formatter.appendValue( ChronoField.YEAR );
break;
case 'z':
formatter.appendOffset( "+HHMM", "+0000" );
break;
case 'Z':
formatter.appendChronologyId();
break;
}
}
}
}
static long fromTable( Map<?, ?> table ) throws LuaException
{
int year = getField( table, "year", -1 );
int month = getField( table, "month", -1 );
int day = getField( table, "day", -1 );
int hour = getField( table, "hour", 12 );
int minute = getField( table, "min", 12 );
int second = getField( table, "sec", 12 );
LocalDateTime time = LocalDateTime.of( year, month, day, hour, minute, second );
Boolean isDst = getBoolField( table, "isdst" );
if( isDst != null )
{
boolean requireDst = isDst;
for( ZoneOffset possibleOffset : ZoneOffset.systemDefault().getRules().getValidOffsets( time ) )
{
Instant instant = time.toInstant( possibleOffset );
if( possibleOffset.getRules().getDaylightSavings( instant ).isZero() == requireDst )
{
return instant.getEpochSecond();
}
}
}
ZoneOffset offset = ZoneOffset.systemDefault().getRules().getOffset( time );
return time.toInstant( offset ).getEpochSecond();
}
static Map<String, ?> toTable( TemporalAccessor date, ZoneId offset, Instant instant )
{
HashMap<String, Object> table = new HashMap<>( 9 );
table.put( "year", date.getLong( ChronoField.YEAR ) );
table.put( "month", date.getLong( ChronoField.MONTH_OF_YEAR ) );
table.put( "day", date.getLong( ChronoField.DAY_OF_MONTH ) );
table.put( "hour", date.getLong( ChronoField.HOUR_OF_DAY ) );
table.put( "min", date.getLong( ChronoField.MINUTE_OF_HOUR ) );
table.put( "sec", date.getLong( ChronoField.SECOND_OF_MINUTE ) );
table.put( "wday", date.getLong( WeekFields.SUNDAY_START.dayOfWeek() ) );
table.put( "yday", date.getLong( ChronoField.DAY_OF_YEAR ) );
table.put( "isdst", offset.getRules().isDaylightSavings( instant ) );
return table;
}
private static int getField( Map<?, ?> table, String field, int def ) throws LuaException
{
Object value = table.get( field );
if( value instanceof Number ) return ((Number) value).intValue();
if( def < 0 ) throw new LuaException( "field \"" + field + "\" missing in date table" );
return def;
}
private static Boolean getBoolField( Map<?, ?> table, String field ) throws LuaException
{
Object value = table.get( field );
if( value instanceof Boolean || value == null ) return (Boolean) value;
throw new LuaException( "field \"" + field + "\" missing in date table" );
}
private static final TemporalField CENTURY = map( ChronoField.YEAR, ValueRange.of( 0, 6 ), x -> (x / 100) % 100 );
private static final TemporalField ZERO_WEEK = map( WeekFields.SUNDAY_START.dayOfWeek(), ValueRange.of( 0, 6 ), x -> x - 1 );
private static TemporalField map( TemporalField field, ValueRange range, LongUnaryOperator convert )
{
return new TemporalField()
{
private final ValueRange range = ValueRange.of( 0, 99 );
@Override
public TemporalUnit getBaseUnit()
{
return field.getBaseUnit();
}
@Override
public TemporalUnit getRangeUnit()
{
return field.getRangeUnit();
}
@Override
public ValueRange range()
{
return range;
}
@Override
public boolean isDateBased()
{
return field.isDateBased();
}
@Override
public boolean isTimeBased()
{
return field.isTimeBased();
}
@Override
public boolean isSupportedBy( TemporalAccessor temporal )
{
return field.isSupportedBy( temporal );
}
@Override
public ValueRange rangeRefinedBy( TemporalAccessor temporal )
{
return range;
}
@Override
public long getFrom( TemporalAccessor temporal )
{
return convert.applyAsLong( temporal.getLong( field ) );
}
@Override
@SuppressWarnings( "unchecked" )
public <R extends Temporal> R adjustInto( R temporal, long newValue )
{
return (R) temporal.with( field, newValue );
}
};
}
}

View File

@@ -12,11 +12,6 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatterBuilder;
import java.util.*;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
@@ -44,7 +39,7 @@ public class OSAPI implements ILuaAPI
}
}
private static class Alarm implements Comparable<Alarm>
private class Alarm implements Comparable<Alarm>
{
public final double m_time;
public final int m_day;
@@ -115,7 +110,7 @@ public class OSAPI implements ILuaAPI
{
Map.Entry<Integer, Timer> entry = it.next();
Timer timer = entry.getValue();
timer.m_ticksLeft--;
timer.m_ticksLeft = timer.m_ticksLeft - 1;
if( timer.m_ticksLeft <= 0 )
{
// Queue the "timer" event
@@ -189,12 +184,11 @@ public class OSAPI implements ILuaAPI
"day",
"cancelTimer",
"cancelAlarm",
"epoch",
"date",
"epoch"
};
}
private static float getTimeForCalendar( Calendar c )
private float getTimeForCalendar( Calendar c )
{
float time = c.get( Calendar.HOUR_OF_DAY );
time += c.get( Calendar.MINUTE ) / 60.0f;
@@ -202,9 +196,9 @@ public class OSAPI implements ILuaAPI
return time;
}
private static int getDayForCalendar( Calendar c )
private int getDayForCalendar( Calendar c )
{
GregorianCalendar g = c instanceof GregorianCalendar ? (GregorianCalendar) c : new GregorianCalendar();
GregorianCalendar g = (c instanceof GregorianCalendar) ? (GregorianCalendar) c : new GregorianCalendar();
int year = c.get( Calendar.YEAR );
int day = 0;
for( int y = 1970; y < year; y++ )
@@ -215,7 +209,7 @@ public class OSAPI implements ILuaAPI
return day;
}
private static long getEpochForCalendar( Calendar c )
private long getEpochForCalendar( Calendar c )
{
return c.getTime().getTime();
}
@@ -225,9 +219,12 @@ public class OSAPI implements ILuaAPI
{
switch( method )
{
case 0: // queueEvent
case 0:
{
// queueEvent
queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) );
return null;
}
case 1:
{
// startTimer
@@ -248,20 +245,29 @@ public class OSAPI implements ILuaAPI
}
synchronized( m_alarms )
{
int day = time > m_time ? m_day : m_day + 1;
int day = (time > m_time) ? m_day : (m_day + 1);
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
return new Object[] { m_nextAlarmToken++ };
}
}
case 3: // shutdown
case 3:
{
// shutdown
m_apiEnvironment.shutdown();
return null;
case 4: // reboot
}
case 4:
{
// reboot
m_apiEnvironment.reboot();
return null;
}
case 5:
case 6: // computerID/getComputerID
case 6:
{
// computerID/getComputerID
return new Object[] { getComputerID() };
}
case 7:
{
// setComputerLabel
@@ -280,19 +286,19 @@ public class OSAPI implements ILuaAPI
}
return null;
}
case 10: // clock
case 10:
{
// clock
synchronized( m_timers )
{
return new Object[] { m_clock * 0.05 };
}
}
case 11:
{
// time
Object value = args.length > 0 ? args[0] : null;
if( value instanceof Map ) return new Object[] { LuaDateTime.fromTable( (Map<?, ?>) value ) };
String param = optString( args, 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
switch( param )
{
case "utc":
{
@@ -320,7 +326,7 @@ public class OSAPI implements ILuaAPI
{
// day
String param = optString( args, 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
switch( param )
{
case "utc":
{
@@ -350,7 +356,10 @@ public class OSAPI implements ILuaAPI
int token = getInt( args, 0 );
synchronized( m_timers )
{
m_timers.remove( token );
if( m_timers.containsKey( token ) )
{
m_timers.remove( token );
}
}
return null;
}
@@ -360,14 +369,18 @@ public class OSAPI implements ILuaAPI
int token = getInt( args, 0 );
synchronized( m_alarms )
{
m_alarms.remove( token );
if( m_alarms.containsKey( token ) )
{
m_alarms.remove( token );
}
}
return null;
}
case 15: // epoch
case 15:
{
// epoch
String param = optString( args, 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
switch( param )
{
case "utc":
{
@@ -393,36 +406,10 @@ public class OSAPI implements ILuaAPI
throw new LuaException( "Unsupported operation" );
}
}
case 16: // date
{
String format = optString( args, 0, "%c" );
long time = optLong( args, 1, Instant.now().getEpochSecond() );
Instant instant = Instant.ofEpochSecond( time );
ZonedDateTime date;
ZoneOffset offset;
boolean isDst;
if( format.startsWith( "!" ) )
{
offset = ZoneOffset.UTC;
date = ZonedDateTime.ofInstant( instant, offset );
format = format.substring( 1 );
}
else
{
ZoneId id = ZoneId.systemDefault();
offset = id.getRules().getOffset( instant );
date = ZonedDateTime.ofInstant( instant, id );
}
if( format.equals( "*t" ) ) return new Object[] { LuaDateTime.toTable( date, offset, instant ) };
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
LuaDateTime.format( formatter, format, offset );
return new Object[] { formatter.toFormatter( Locale.ROOT ).format( date ) };
}
default:
{
return null;
}
}
}

View File

@@ -12,7 +12,9 @@ import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerThread;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
@@ -45,8 +47,8 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
m_type = peripheral.getType();
m_methods = peripheral.getMethodNames();
assert m_type != null;
assert m_methods != null;
assert (m_type != null);
assert (m_methods != null);
m_methodMap = new HashMap<>();
for( int i = 0; i < m_methods.length; i++ )
@@ -229,9 +231,9 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
private final PeripheralWrapper[] m_peripherals;
private boolean m_running;
public PeripheralAPI( IAPIEnvironment environment )
public PeripheralAPI( IAPIEnvironment _environment )
{
m_environment = environment;
m_environment = _environment;
m_environment.setPeripheralChangeListener( this );
m_peripherals = new PeripheralWrapper[6];
@@ -246,33 +248,76 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
// IPeripheralChangeListener
@Override
public void onPeripheralChanged( ComputerSide side, IPeripheral newPeripheral )
public void onPeripheralChanged( int side, IPeripheral newPeripheral )
{
synchronized( m_peripherals )
{
int index = side.ordinal();
if( m_peripherals[index] != null )
if( m_peripherals[side] != null )
{
// Queue a detachment
final PeripheralWrapper wrapper = m_peripherals[index];
if( wrapper.isAttached() ) wrapper.detach();
final PeripheralWrapper wrapper = m_peripherals[side];
ComputerThread.queueTask( new ITask()
{
@Override
public Computer getOwner()
{
return m_environment.getComputer();
}
@Override
public void execute()
{
synchronized( m_peripherals )
{
if( wrapper.isAttached() )
{
wrapper.detach();
}
}
}
}, null );
// Queue a detachment event
m_environment.queueEvent( "peripheral_detach", new Object[] { side.getName() } );
m_environment.queueEvent( "peripheral_detach", new Object[] { Computer.s_sideNames[side] } );
}
// Assign the new peripheral
m_peripherals[index] = newPeripheral == null ? null
: new PeripheralWrapper( newPeripheral, side.getName() );
if( newPeripheral != null )
{
m_peripherals[side] = new PeripheralWrapper( newPeripheral, Computer.s_sideNames[side] );
}
else
{
m_peripherals[side] = null;
}
if( m_peripherals[index] != null )
if( m_peripherals[side] != null )
{
// Queue an attachment
final PeripheralWrapper wrapper = m_peripherals[index];
if( m_running && !wrapper.isAttached() ) wrapper.attach();
final PeripheralWrapper wrapper = m_peripherals[side];
ComputerThread.queueTask( new ITask()
{
@Override
public Computer getOwner()
{
return m_environment.getComputer();
}
@Override
public void execute()
{
synchronized( m_peripherals )
{
if( m_running && !wrapper.isAttached() )
{
wrapper.attach();
}
}
}
}, null );
// Queue an attachment event
m_environment.queueEvent( "peripheral", new Object[] { side.getName() } );
m_environment.queueEvent( "peripheral", new Object[] { Computer.s_sideNames[side] } );
}
}
}
@@ -296,7 +341,10 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
for( int i = 0; i < 6; i++ )
{
PeripheralWrapper wrapper = m_peripherals[i];
if( wrapper != null && !wrapper.isAttached() ) wrapper.attach();
if( wrapper != null && !wrapper.isAttached() )
{
wrapper.attach();
}
}
}
}
@@ -339,13 +387,16 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
{
// isPresent
boolean present = false;
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side != null )
int side = parseSide( args );
if( side >= 0 )
{
synchronized( m_peripherals )
{
PeripheralWrapper p = m_peripherals[side.ordinal()];
if( p != null ) present = true;
PeripheralWrapper p = m_peripherals[side];
if( p != null )
{
present = true;
}
}
}
return new Object[] { present };
@@ -353,14 +404,21 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
case 1:
{
// getType
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side != null )
String type = null;
int side = parseSide( args );
if( side >= 0 )
{
String type = null;
synchronized( m_peripherals )
{
PeripheralWrapper p = m_peripherals[side.ordinal()];
if( p != null ) return new Object[] { p.getType() };
PeripheralWrapper p = m_peripherals[side];
if( p != null )
{
type = p.getType();
}
}
if( type != null )
{
return new Object[] { type };
}
}
return null;
@@ -369,12 +427,12 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
{
// getMethods
String[] methods = null;
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side != null )
int side = parseSide( args );
if( side >= 0 )
{
synchronized( m_peripherals )
{
PeripheralWrapper p = m_peripherals[side.ordinal()];
PeripheralWrapper p = m_peripherals[side];
if( p != null )
{
methods = p.getMethods();
@@ -395,16 +453,16 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
case 3:
{
// call
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
int side = parseSide( args );
String methodName = getString( args, 1 );
Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
if( side != null )
if( side >= 0 )
{
PeripheralWrapper p;
synchronized( m_peripherals )
{
p = m_peripherals[side.ordinal()];
p = m_peripherals[side];
}
if( p != null )
{
@@ -414,7 +472,24 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
throw new LuaException( "No peripheral attached" );
}
default:
{
return null;
}
}
}
// Privates
private int parseSide( Object[] args ) throws LuaException
{
String side = getString( args, 0 );
for( int n = 0; n < Computer.s_sideNames.length; n++ )
{
if( side.equals( Computer.s_sideNames[n] ) )
{
return n;
}
}
return -1;
}
}

View File

@@ -9,7 +9,7 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.Computer;
import javax.annotation.Nonnull;
import java.util.HashMap;
@@ -65,49 +65,65 @@ public class RedstoneAPI implements ILuaAPI
{
// getSides
Map<Object, Object> table = new HashMap<>();
for( int i = 0; i < ComputerSide.NAMES.length; i++ )
for( int i = 0; i < Computer.s_sideNames.length; i++ )
{
table.put( i + 1, ComputerSide.NAMES[i] );
table.put( i + 1, Computer.s_sideNames[i] );
}
return new Object[] { table };
}
case 1:
{
// setOutput
ComputerSide side = parseSide( args );
int side = parseSide( args );
boolean output = getBoolean( args, 1 );
m_environment.setOutput( side, output ? 15 : 0 );
return null;
}
case 2: // getOutput
return new Object[] { m_environment.getOutput( parseSide( args ) ) > 0 };
case 3: // getInput
return new Object[] { m_environment.getInput( parseSide( args ) ) > 0 };
case 2:
{
// getOutput
int side = parseSide( args );
return new Object[] { m_environment.getOutput( side ) > 0 };
}
case 3:
{
// getInput
int side = parseSide( args );
return new Object[] { m_environment.getInput( side ) > 0 };
}
case 4:
{
// setBundledOutput
ComputerSide side = parseSide( args );
int side = parseSide( args );
int output = getInt( args, 1 );
m_environment.setBundledOutput( side, output );
return null;
}
case 5: // getBundledOutput
return new Object[] { m_environment.getBundledOutput( parseSide( args ) ) };
case 6: // getBundledInput
return new Object[] { m_environment.getBundledInput( parseSide( args ) ) };
case 5:
{
// getBundledOutput
int side = parseSide( args );
return new Object[] { m_environment.getBundledOutput( side ) };
}
case 6:
{
// getBundledInput
int side = parseSide( args );
return new Object[] { m_environment.getBundledInput( side ) };
}
case 7:
{
// testBundledInput
ComputerSide side = parseSide( args );
int side = parseSide( args );
int mask = getInt( args, 1 );
int input = m_environment.getBundledInput( side );
return new Object[] { (input & mask) == mask };
return new Object[] { ((input & mask) == mask) };
}
case 8:
case 9:
{
// setAnalogOutput/setAnalogueOutput
ComputerSide side = parseSide( args );
int side = parseSide( args );
int output = getInt( args, 1 );
if( output < 0 || output > 15 )
{
@@ -117,20 +133,36 @@ public class RedstoneAPI implements ILuaAPI
return null;
}
case 10:
case 11: // getAnalogOutput/getAnalogueOutput
return new Object[] { m_environment.getOutput( parseSide( args ) ) };
case 11:
{
// getAnalogOutput/getAnalogueOutput
int side = parseSide( args );
return new Object[] { m_environment.getOutput( side ) };
}
case 12:
case 13: // getAnalogInput/getAnalogueInput
return new Object[] { m_environment.getInput( parseSide( args ) ) };
case 13:
{
// getAnalogInput/getAnalogueInput
int side = parseSide( args );
return new Object[] { m_environment.getInput( side ) };
}
default:
{
return null;
}
}
}
private static ComputerSide parseSide( Object[] args ) throws LuaException
private int parseSide( Object[] args ) throws LuaException
{
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side == null ) throw new LuaException( "Invalid side." );
return side;
String side = getString( args, 0 );
for( int n = 0; n < Computer.s_sideNames.length; n++ )
{
if( side.equals( Computer.s_sideNames[n] ) )
{
return n;
}
}
throw new LuaException( "Invalid side." );
}
}

View File

@@ -11,7 +11,6 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import org.apache.commons.lang3.ArrayUtils;
@@ -24,10 +23,10 @@ public class TermAPI implements ILuaAPI
private final Terminal m_terminal;
private final IComputerEnvironment m_environment;
public TermAPI( IAPIEnvironment environment )
public TermAPI( IAPIEnvironment _environment )
{
m_terminal = environment.getTerminal();
m_environment = environment.getComputerEnvironment();
m_terminal = _environment.getTerminal();
m_environment = _environment.getComputerEnvironment();
}
@Override
@@ -66,8 +65,6 @@ public class TermAPI implements ILuaAPI
"setPaletteColor",
"getPaletteColour",
"getPaletteColor",
"nativePaletteColour",
"nativePaletteColor",
"getCursorBlink",
};
}
@@ -111,7 +108,16 @@ public class TermAPI implements ILuaAPI
case 0:
{
// write
String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
String text;
if( args.length > 0 && args[0] != null )
{
text = args[0].toString();
}
else
{
text = "";
}
synchronized( m_terminal )
{
m_terminal.write( text );
@@ -172,18 +178,24 @@ public class TermAPI implements ILuaAPI
}
return new Object[] { width, height };
}
case 6: // clear
case 6:
{
// clear
synchronized( m_terminal )
{
m_terminal.clear();
}
return null;
case 7: // clearLine
}
case 7:
{
// clearLine
synchronized( m_terminal )
{
m_terminal.clearLine();
}
return null;
}
case 8:
case 9:
{
@@ -207,14 +219,23 @@ public class TermAPI implements ILuaAPI
return null;
}
case 12:
case 13: // isColour/isColor
case 13:
{
// isColour/isColor
return new Object[] { m_environment.isColour() };
}
case 14:
case 15: // getTextColour/getTextColor
case 15:
{
// getTextColour/getTextColor
return encodeColour( m_terminal.getTextColour() );
}
case 16:
case 17: // getBackgroundColour/getBackgroundColor
case 17:
{
// getBackgroundColour/getBackgroundColor
return encodeColour( m_terminal.getBackgroundColour() );
}
case 18:
{
// blit
@@ -268,23 +289,12 @@ public class TermAPI implements ILuaAPI
return null;
}
case 23:
case 24:
{
// nativePaletteColour/nativePaletteColor
int colour = 15 - parseColour( args );
Colour c = Colour.fromInt( colour );
float[] rgb = c.getRGB();
Object[] rgbObj = new Object[rgb.length];
for( int i = 0; i < rgbObj.length; ++i ) rgbObj[i] = rgb[i];
return rgbObj;
}
case 25:
// getCursorBlink
return new Object[] { m_terminal.getCursorBlink() };
default:
{
return null;
}
}
}

View File

@@ -6,11 +6,13 @@
package dan200.computercraft.core.apis.handles;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.SeekableByteChannel;
import java.util.Objects;
/**
* A seekable, readable byte channel which is backed by a simple byte array.
@@ -28,10 +30,10 @@ public class ArrayByteChannel implements SeekableByteChannel
}
@Override
public int read( ByteBuffer destination ) throws ClosedChannelException
public int read( ByteBuffer destination ) throws IOException
{
if( closed ) throw new ClosedChannelException();
Objects.requireNonNull( destination, "destination" );
Preconditions.checkNotNull( destination, "destination" );
if( position >= backing.length ) return -1;
@@ -42,21 +44,21 @@ public class ArrayByteChannel implements SeekableByteChannel
}
@Override
public int write( ByteBuffer src ) throws ClosedChannelException
public int write( ByteBuffer src ) throws IOException
{
if( closed ) throw new ClosedChannelException();
throw new NonWritableChannelException();
}
@Override
public long position() throws ClosedChannelException
public long position() throws IOException
{
if( closed ) throw new ClosedChannelException();
return position;
}
@Override
public SeekableByteChannel position( long newPosition ) throws ClosedChannelException
public SeekableByteChannel position( long newPosition ) throws IOException
{
if( closed ) throw new ClosedChannelException();
if( newPosition < 0 || newPosition > Integer.MAX_VALUE )
@@ -68,14 +70,14 @@ public class ArrayByteChannel implements SeekableByteChannel
}
@Override
public long size() throws ClosedChannelException
public long size() throws IOException
{
if( closed ) throw new ClosedChannelException();
return backing.length;
}
@Override
public SeekableByteChannel truncate( long size ) throws ClosedChannelException
public SeekableByteChannel truncate( long size ) throws IOException
{
if( closed ) throw new ClosedChannelException();
throw new NonWritableChannelException();

View File

@@ -38,8 +38,8 @@ public class BinaryReadableHandle extends HandleGeneric
public BinaryReadableHandle( ReadableByteChannel channel, Closeable closeable )
{
super( closeable );
m_reader = channel;
m_seekable = asSeekable( channel );
this.m_reader = channel;
this.m_seekable = asSeekable( channel );
}
public BinaryReadableHandle( ReadableByteChannel channel )
@@ -169,42 +169,27 @@ public class BinaryReadableHandle extends HandleGeneric
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
boolean readAnything = false, readRc = false;
boolean readAnything = false;
while( true )
{
single.clear();
int read = m_reader.read( single );
if( read <= 0 )
{
// Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it
// back.
if( readRc ) stream.write( '\r' );
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
int r = m_reader.read( single );
if( r == -1 ) break;
readAnything = true;
byte chr = single.get( 0 );
if( chr == '\n' )
byte b = single.get( 0 );
if( b == '\n' )
{
if( withTrailing )
{
if( readRc ) stream.write( '\r' );
stream.write( chr );
}
return new Object[] { stream.toByteArray() };
if( withTrailing ) stream.write( b );
break;
}
else
{
// We want to skip \r\n, but obviously need to include cases where \r is not followed by \n.
// Note, this behaviour is non-standard compliant (strictly speaking we should have no
// special logic for \r), but we preserve compatibility with EncodedReadableHandle and
// previous behaviour of the io library.
if( readRc ) stream.write( '\r' );
readRc = chr == '\r';
if( !readRc ) stream.write( chr );
stream.write( b );
}
}
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
catch( IOException e )
{

View File

@@ -32,8 +32,8 @@ public class BinaryWritableHandle extends HandleGeneric
public BinaryWritableHandle( WritableByteChannel channel, Closeable closeable )
{
super( closeable );
m_writer = channel;
m_seekable = asSeekable( channel );
this.m_writer = channel;
this.m_seekable = asSeekable( channel );
}
public BinaryWritableHandle( WritableByteChannel channel )

View File

@@ -32,7 +32,7 @@ public class EncodedReadableHandle extends HandleGeneric
public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable )
{
super( closable );
m_reader = reader;
this.m_reader = reader;
}
public EncodedReadableHandle( @Nonnull BufferedReader reader )
@@ -84,7 +84,7 @@ public class EncodedReadableHandle extends HandleGeneric
checkOpen();
try
{
StringBuilder result = new StringBuilder();
StringBuilder result = new StringBuilder( "" );
String line = m_reader.readLine();
while( line != null )
{

View File

@@ -27,7 +27,7 @@ public class EncodedWritableHandle extends HandleGeneric
public EncodedWritableHandle( @Nonnull BufferedWriter writer, @Nonnull Closeable closable )
{
super( closable );
m_writer = writer;
this.m_writer = writer;
}
public EncodedWritableHandle( @Nonnull BufferedWriter writer )

View File

@@ -26,7 +26,7 @@ public abstract class HandleGeneric implements ILuaObject
protected HandleGeneric( @Nonnull Closeable closable )
{
m_closable = closable;
this.m_closable = closable;
}
protected void checkOpen() throws LuaException
@@ -46,7 +46,7 @@ public abstract class HandleGeneric implements ILuaObject
*
* @param channel The channel to seek in
* @param args The Lua arguments to process, like Lua's {@code file:seek}.
* @return The new position of the file, or null if some error occurred.
* @return The new position of the file, or null if some error occured.
* @throws LuaException If the arguments were invalid
* @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a>
*/

View File

@@ -31,7 +31,7 @@ public class CheckUrl extends Resource<CheckUrl>
super( limiter );
this.environment = environment;
this.address = address;
host = uri.getHost();
this.host = uri.getHost();
}
public void run()

View File

@@ -140,6 +140,6 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
public static void cleanup()
{
Reference<?> reference;
while( (reference = QUEUE.poll()) != null ) ((CloseReference<?>) reference).resource.close();
while( (reference = QUEUE.poll()) != null ) ((CloseReference) reference).resource.close();
}
}

View File

@@ -32,7 +32,7 @@ public class ResourceGroup<T extends Resource<T>>
public ResourceGroup()
{
limit = ZERO;
this.limit = ZERO;
}
public void startup()

View File

@@ -67,12 +67,12 @@ public class HttpRequest extends Resource<HttpRequest>
super( limiter );
this.environment = environment;
this.address = address;
postBuffer = postText != null
this.postBuffer = postText != null
? Unpooled.wrappedBuffer( postText.getBytes( StandardCharsets.UTF_8 ) )
: Unpooled.buffer( 0 );
this.headers = headers;
this.binary = binary;
redirects = new AtomicInteger( followRedirects ? MAX_REDIRECTS : 0 );
this.redirects = new AtomicInteger( followRedirects ? MAX_REDIRECTS : 0 );
if( postText != null )
{
@@ -113,7 +113,6 @@ public class HttpRequest extends Resource<HttpRequest>
{
// Validate the URL
if( url.getScheme() == null ) throw new HTTPRequestException( "Must specify http or https" );
if( url.getHost() == null ) throw new HTTPRequestException( "URL malformed" );
String scheme = url.getScheme().toLowerCase( Locale.ROOT );
if( !scheme.equalsIgnoreCase( "http" ) && !scheme.equalsIgnoreCase( "https" ) )

View File

@@ -37,7 +37,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
/**
* Same as {@link io.netty.handler.codec.MessageAggregator}.
*/
private static final int DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS = 1024;
private static final int DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS = 1024;
private static final byte[] EMPTY_BYTES = new byte[0];
@@ -147,7 +147,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
if( responseBody == null )
{
responseBody = ctx.alloc().compositeBuffer( DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS );
responseBody = ctx.alloc().compositeBuffer( DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS );
}
ByteBuf partial = content.content();

View File

@@ -95,7 +95,7 @@ public class Websocket extends Resource<Websocket>
{
try
{
uri = new URI( "ws://" + uri );
uri = new URI( "ws://" + uri.toString() );
}
catch( URISyntaxException e )
{
@@ -186,7 +186,7 @@ public class Websocket extends Resource<Websocket>
WebsocketHandle handle = new WebsocketHandle( this, channel );
environment().queueEvent( SUCCESS_EVENT, new Object[] { address, handle } );
websocketHandle = createOwnerReference( handle );
this.websocketHandle = createOwnerReference( handle );
checkClosed();
}
@@ -216,7 +216,7 @@ public class Websocket extends Resource<Websocket>
executorFuture = closeFuture( executorFuture );
connectFuture = closeChannel( connectFuture );
WeakReference<WebsocketHandle> websocketHandleRef = websocketHandle;
WeakReference<WebsocketHandle> websocketHandleRef = this.websocketHandle;
WebsocketHandle websocketHandle = websocketHandleRef == null ? null : websocketHandleRef.get();
if( websocketHandle != null ) IoUtil.closeQuietly( websocketHandle );
this.websocketHandle = null;

View File

@@ -24,7 +24,6 @@ import java.io.Closeable;
import java.util.Arrays;
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
import static dan200.computercraft.core.apis.http.websocket.Websocket.CLOSE_EVENT;
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
public class WebsocketHandle implements ILuaObject, Closeable
@@ -54,18 +53,15 @@ public class WebsocketHandle implements ILuaObject, Closeable
switch( method )
{
case 0: // receive
checkOpen();
while( true )
{
Object[] event = context.pullEvent( null );
if( event.length >= 3 && Objects.equal( event[0], MESSAGE_EVENT ) && Objects.equal( event[1], websocket.address() ) )
checkOpen();
Object[] event = context.pullEvent( MESSAGE_EVENT );
if( event.length >= 3 && Objects.equal( event[1], websocket.address() ) )
{
return Arrays.copyOfRange( event, 2, event.length );
}
else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed )
{
return null;
}
}
case 1: // send

View File

@@ -83,11 +83,6 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
CloseWebSocketFrame closeFrame = (CloseWebSocketFrame) frame;
websocket.close( closeFrame.statusCode(), closeFrame.reasonText() );
}
else if( frame instanceof PingWebSocketFrame )
{
frame.content().retain();
ctx.channel().writeAndFlush( new PongWebSocketFrame( frame.content() ) );
}
}
@Override
@@ -113,13 +108,6 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
message = "Could not connect";
}
if( handshaker.isHandshakeComplete() )
{
websocket.close( -1, message );
}
else
{
websocket.failure( message );
}
websocket.failure( message );
}
}

View File

@@ -1,68 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A wrapper for {@link ILuaAPI}s which cleans up after a {@link ComputerSystem} when the computer is shutdown.
*/
final class ApiWrapper implements ILuaAPI
{
private final ILuaAPI delegate;
private final ComputerSystem system;
ApiWrapper( ILuaAPI delegate, ComputerSystem system )
{
this.delegate = delegate;
this.system = system;
}
@Override
public String[] getNames()
{
return delegate.getNames();
}
@Override
public void startup()
{
delegate.startup();
}
@Override
public void update()
{
delegate.update();
}
@Override
public void shutdown()
{
delegate.shutdown();
system.unmountAll();
}
@Nonnull
@Override
public String[] getMethodNames()
{
return delegate.getMethodNames();
}
@Nullable
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return delegate.callMethod( context, method, arguments );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,668 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.core.apis.*;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.lua.MachineResult;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.IoUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* The main task queue and executor for a single computer. This handles turning on and off a computer, as well as
* running events.
*
* When the computer is instructed to turn on or off, or handle an event, we queue a task and register this to be
* executed on the {@link ComputerThread}. Note, as we may be starting many events in a single tick, the external
* cannot lock on anything which may be held for a long time.
*
* The executor is effectively composed of two separate queues. Firstly, we have a "single element" queue
* {@link #command} which determines which state the computer should transition too. This is set by
* {@link #queueStart()} and {@link #queueStop(boolean, boolean)}.
*
* When a computer is on, we simply push any events onto to the {@link #eventQueue}.
*
* Both queues are run from the {@link #work()} method, which tries to execute a command if one exists, or resumes the
* machine with an event otherwise.
*
* One final responsibility for the executor is calling {@link ILuaAPI#update()} every tick, via the {@link #tick()}
* method. This should only be called when the computer is actually on ({@link #isOn}).
*/
final class ComputerExecutor
{
private static final int QUEUE_LIMIT = 256;
private static IMount romMount;
private static final Object romMountLock = new Object();
private final Computer computer;
private final List<ILuaAPI> apis = new ArrayList<>();
final TimeoutState timeout = new TimeoutState();
private FileSystem fileSystem;
private ILuaMachine machine;
/**
* Whether the computer is currently on. This is set to false when a shutdown starts, or when turning on completes
* (but just before the Lua machine is started).
*
* @see #isOnLock
*/
private volatile boolean isOn = false;
/**
* The lock to acquire when you need to modify the "on state" of a computer.
*
* We hold this lock when running any command, and attempt to hold it when updating APIs. This ensures you don't
* update APIs while also starting/stopping them.
*
* @see #isOn
* @see #tick()
* @see #turnOn()
* @see #shutdown()
*/
private final ReentrantLock isOnLock = new ReentrantLock();
/**
* A lock used for any changes to {@link #eventQueue}, {@link #command} or {@link #onComputerQueue}. This will be
* used on the main thread, so locks should be kept as brief as possible.
*/
private final Object queueLock = new Object();
/**
* Determines if this executor is present within {@link ComputerThread}.
*
* @see #queueLock
* @see #enqueue()
* @see #afterWork()
*/
volatile boolean onComputerQueue = false;
/**
* The amount of time this computer has used on a theoretical machine which shares work evenly amongst computers.
*
* @see ComputerThread
*/
long virtualRuntime = 0;
/**
* The last time at which we updated {@link #virtualRuntime}.
*
* @see ComputerThread
*/
long vRuntimeStart;
/**
* The command that {@link #work()} should execute on the computer thread.
*
* One sets the command with {@link #queueStart()} and {@link #queueStop(boolean, boolean)}. Neither of these will
* queue a new event if there is an existing one in the queue.
*
* Note, if command is not {@code null}, then some command is scheduled to be executed. Otherwise it is not
* currently in the queue (or is currently being executed).
*/
private volatile StateCommand command;
/**
* The queue of events which should be executed when this computer is on.
*
* Note, this should be empty if this computer is off - it is cleared on shutdown and when turning on again.
*/
private final Queue<Event> eventQueue = new ArrayDeque<>( 4 );
/**
* Whether we interrupted an event and so should resume it instead of executing another task.
*
* @see #work()
* @see #resumeMachine(String, Object[])
*/
private boolean interruptedEvent = false;
/**
* Whether this executor has been closed, and will no longer accept any incoming commands or events.
*
* @see #queueStop(boolean, boolean)
*/
private boolean closed;
private IWritableMount rootMount;
/**
* The thread the executor is running on. This is non-null when performing work. We use this to ensure we're only
* doing one bit of work at one time.
*
* @see ComputerThread
*/
final AtomicReference<Thread> executingThread = new AtomicReference<>();
ComputerExecutor( Computer computer )
{
// Ensure the computer thread is running as required.
ComputerThread.start();
this.computer = computer;
Environment environment = computer.getEnvironment();
// Add all default APIs to the loaded list.
apis.add( new TermAPI( environment ) );
apis.add( new RedstoneAPI( environment ) );
apis.add( new FSAPI( environment ) );
apis.add( new PeripheralAPI( environment ) );
apis.add( new OSAPI( environment ) );
if( ComputerCraft.http_enable ) apis.add( new HTTPAPI( environment ) );
// Load in the externally registered APIs.
for( ILuaAPIFactory factory : ApiFactories.getAll() )
{
ComputerSystem system = new ComputerSystem( environment );
ILuaAPI api = factory.create( system );
if( api != null ) apis.add( new ApiWrapper( api, system ) );
}
}
boolean isOn()
{
return isOn;
}
FileSystem getFileSystem()
{
return fileSystem;
}
Computer getComputer()
{
return computer;
}
void addApi( ILuaAPI api )
{
apis.add( api );
}
/**
* Schedule this computer to be started if not already on.
*/
void queueStart()
{
synchronized( queueLock )
{
// We should only schedule a start if we're not currently on and there's turn on.
if( closed || isOn || command != null ) return;
command = StateCommand.TURN_ON;
enqueue();
}
}
/**
* Schedule this computer to be stopped if not already on.
*
* @param reboot Reboot the computer after stopping
* @param close Close the computer after stopping.
* @see #closed
*/
void queueStop( boolean reboot, boolean close )
{
synchronized( queueLock )
{
if( closed ) return;
closed = close;
StateCommand newCommand = reboot ? StateCommand.REBOOT : StateCommand.SHUTDOWN;
// We should only schedule a stop if we're currently on and there's no shutdown pending.
if( !isOn || command != null )
{
// If we're closing, set the command just in case.
if( close ) command = newCommand;
return;
}
command = newCommand;
enqueue();
}
}
/**
* Abort this whole computer due to a timeout. This will immediately destroy the Lua machine,
* and then schedule a shutdown.
*/
void abort()
{
ILuaMachine machine = this.machine;
if( machine != null ) machine.close();
synchronized( queueLock )
{
if( closed ) return;
command = StateCommand.ABORT;
if( isOn ) enqueue();
}
}
/**
* Queue an event if the computer is on
*
* @param event The event's name
* @param args The event's arguments
*/
void queueEvent( @Nonnull String event, @Nullable Object[] args )
{
// Events should be skipped if we're not on.
if( !isOn ) return;
synchronized( queueLock )
{
// And if we've got some command in the pipeline, then don't queue events - they'll
// probably be disposed of anyway.
// We also limit the number of events which can be queued.
if( closed || command != null || eventQueue.size() >= QUEUE_LIMIT ) return;
eventQueue.offer( new Event( event, args ) );
enqueue();
}
}
/**
* Add this executor to the {@link ComputerThread} if not already there.
*/
private void enqueue()
{
synchronized( queueLock )
{
if( !onComputerQueue ) ComputerThread.queue( this );
}
}
/**
* Update the internals of the executor.
*/
void tick()
{
if( isOn && isOnLock.tryLock() )
{
// This horrific structure means we don't try to update APIs while the state is being changed
// (and so they may be running startup/shutdown).
// We use tryLock here, as it has minimal delay, and it doesn't matter if we miss an advance at the
// beginning or end of a computer's lifetime.
try
{
if( isOn )
{
// Advance our APIs.
for( ILuaAPI api : apis ) api.update();
}
}
finally
{
isOnLock.unlock();
}
}
}
private IMount getRomMount()
{
if( romMount != null ) return romMount;
synchronized( romMountLock )
{
if( romMount != null ) return romMount;
return romMount = computer.getComputerEnvironment().createResourceMount( "computercraft", "lua/rom" );
}
}
IWritableMount getRootMount()
{
if( rootMount == null )
{
rootMount = computer.getComputerEnvironment().createSaveDirMount(
"computer/" + computer.assignID(),
computer.getComputerEnvironment().getComputerSpaceLimit()
);
}
return rootMount;
}
private FileSystem createFileSystem()
{
FileSystem filesystem = null;
try
{
filesystem = new FileSystem( "hdd", getRootMount() );
IMount romMount = getRomMount();
if( romMount == null )
{
displayFailure( "Cannot mount ROM", null );
return null;
}
filesystem.mount( "rom", "rom", romMount );
return filesystem;
}
catch( FileSystemException e )
{
if( filesystem != null ) filesystem.close();
ComputerCraft.log.error( "Cannot mount computer filesystem", e );
displayFailure( "Cannot mount computer system", null );
return null;
}
}
private ILuaMachine createLuaMachine()
{
// Load the bios resource
InputStream biosStream = null;
try
{
biosStream = computer.getComputerEnvironment().createResourceFile( "computercraft", "lua/bios.lua" );
}
catch( Exception ignored )
{
}
if( biosStream == null )
{
displayFailure( "Error loading bios.lua", null );
return null;
}
// Create the lua machine
ILuaMachine machine = new CobaltLuaMachine( computer, timeout );
// Add the APIs
for( ILuaAPI api : apis ) machine.addAPI( api );
// Start the machine running the bios resource
MachineResult result = machine.loadBios( biosStream );
IoUtil.closeQuietly( biosStream );
if( result.isError() )
{
machine.close();
displayFailure( "Error loading bios.lua", result.getMessage() );
return null;
}
return machine;
}
private void turnOn() throws InterruptedException
{
isOnLock.lockInterruptibly();
try
{
// Reset the terminal and event queue
computer.getTerminal().reset();
interruptedEvent = false;
synchronized( queueLock )
{
eventQueue.clear();
}
// Init filesystem
if( (fileSystem = createFileSystem()) == null )
{
shutdown();
return;
}
// Init APIs
for( ILuaAPI api : apis ) api.startup();
// Init lua
if( (machine = createLuaMachine()) == null )
{
shutdown();
return;
}
// Initialisation has finished, so let's mark ourselves as on.
isOn = true;
computer.markChanged();
}
finally
{
isOnLock.unlock();
}
// Now actually start the computer, now that everything is set up.
resumeMachine( null, null );
}
private void shutdown() throws InterruptedException
{
isOnLock.lockInterruptibly();
try
{
isOn = false;
interruptedEvent = false;
synchronized( queueLock )
{
eventQueue.clear();
}
// Shutdown Lua machine
if( machine != null )
{
machine.close();
machine = null;
}
// Shutdown our APIs
for( ILuaAPI api : apis ) api.shutdown();
// Unload filesystem
if( fileSystem != null )
{
fileSystem.close();
fileSystem = null;
}
computer.getEnvironment().resetOutput();
computer.markChanged();
}
finally
{
isOnLock.unlock();
}
}
/**
* Called before calling {@link #work()}, setting up any important state.
*/
void beforeWork()
{
vRuntimeStart = System.nanoTime();
timeout.startTimer();
}
/**
* Called after executing {@link #work()}.
*
* @return If we have more work to do.
*/
boolean afterWork()
{
if( interruptedEvent )
{
timeout.pauseTimer();
}
else
{
timeout.stopTimer();
}
Tracking.addTaskTiming( getComputer(), timeout.nanoCurrent() );
if( interruptedEvent ) return true;
synchronized( queueLock )
{
if( eventQueue.isEmpty() && command == null ) return onComputerQueue = false;
return true;
}
}
/**
* The main worker function, called by {@link ComputerThread}.
*
* This either executes a {@link StateCommand} or attempts to run an event
*
* @throws InterruptedException If various locks could not be acquired.
* @see #command
* @see #eventQueue
*/
void work() throws InterruptedException
{
if( interruptedEvent )
{
interruptedEvent = false;
if( machine != null )
{
resumeMachine( null, null );
return;
}
}
StateCommand command;
Event event = null;
synchronized( queueLock )
{
command = this.command;
this.command = null;
// If we've no command, pull something from the event queue instead.
if( command == null )
{
if( !isOn )
{
// We're not on and had no command, but we had work queued. This should never happen, so clear
// the event queue just in case.
eventQueue.clear();
return;
}
event = eventQueue.poll();
}
}
if( command != null )
{
switch( command )
{
case TURN_ON:
if( isOn ) return;
turnOn();
break;
case SHUTDOWN:
if( !isOn ) return;
computer.getTerminal().reset();
shutdown();
break;
case REBOOT:
if( !isOn ) return;
computer.getTerminal().reset();
shutdown();
computer.turnOn();
break;
case ABORT:
if( !isOn ) return;
displayFailure( "Error running computer", TimeoutState.ABORT_MESSAGE );
shutdown();
break;
}
}
else if( event != null )
{
resumeMachine( event.name, event.args );
}
}
private void displayFailure( String message, String extra )
{
Terminal terminal = computer.getTerminal();
boolean colour = computer.getComputerEnvironment().isColour();
terminal.reset();
// Display our primary error message
if( colour ) terminal.setTextColour( 15 - Colour.Red.ordinal() );
terminal.write( message );
if( extra != null )
{
// Display any additional information. This generally comes from the Lua Machine, such as compilation or
// runtime errors.
terminal.setCursorPos( 0, terminal.getCursorY() + 1 );
terminal.write( extra );
}
// And display our generic "CC may be installed incorrectly" message.
terminal.setCursorPos( 0, terminal.getCursorY() + 1 );
if( colour ) terminal.setTextColour( 15 - Colour.White.ordinal() );
terminal.write( "ComputerCraft may be installed incorrectly" );
}
private void resumeMachine( String event, Object[] args ) throws InterruptedException
{
MachineResult result = machine.handleEvent( event, args );
interruptedEvent = result.isPause();
if( !result.isError() ) return;
displayFailure( "Error running computer", result.getMessage() );
shutdown();
}
private enum StateCommand
{
TURN_ON,
SHUTDOWN,
REBOOT,
ABORT,
}
private static final class Event
{
final String name;
final Object[] args;
private Event( String name, Object[] args )
{
this.name = name;
this.args = args;
}
}
}

View File

@@ -1,56 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A side on a computer. Unlike {@link net.minecraft.util.EnumFacing}, this is relative to the direction the computer is
* facing..
*/
public enum ComputerSide
{
BOTTOM( "bottom" ),
TOP( "top" ),
BACK( "back" ),
FRONT( "front" ),
RIGHT( "right" ),
LEFT( "left" );
public static final String[] NAMES = new String[] { "bottom", "top", "back", "front", "right", "left" };
public static final int COUNT = 6;
private static final ComputerSide[] VALUES = values();
private final String name;
ComputerSide( String name ) {this.name = name;}
@Nonnull
public static ComputerSide valueOf( int side )
{
return VALUES[side];
}
@Nullable
public static ComputerSide valueOfInsensitive( @Nonnull String name )
{
for( ComputerSide side : VALUES )
{
if( side.name.equalsIgnoreCase( name ) ) return side;
}
return null;
}
public String getName()
{
return name;
}
}

View File

@@ -1,58 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.lua.IComputerSystem;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.core.apis.ComputerAccess;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Implementation of {@link IComputerAccess}/{@link IComputerSystem} for usage by externally registered APIs.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
* @see ILuaAPIFactory
* @see ApiWrapper
*/
public class ComputerSystem extends ComputerAccess implements IComputerSystem
{
private final IAPIEnvironment environment;
ComputerSystem( IAPIEnvironment environment )
{
super( environment );
this.environment = environment;
}
@Nonnull
@Override
public String getAttachmentName()
{
return "computer";
}
@Nullable
@Override
public IFileSystem getFileSystem()
{
FileSystem fs = environment.getFileSystem();
return fs == null ? null : fs.getMountWrapper();
}
@Nullable
@Override
public String getLabel()
{
return environment.getLabel();
}
}

View File

@@ -7,352 +7,153 @@
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.util.ThreadUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.TreeSet;
import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
import static dan200.computercraft.core.computer.TimeoutState.ABORT_TIMEOUT;
import static dan200.computercraft.core.computer.TimeoutState.TIMEOUT;
/**
* Responsible for running all tasks from a {@link Computer}.
*
* This is split into two components: the {@link TaskRunner}s, which pull an executor from the queue and execute it, and
* a single {@link Monitor} which observes all runners and kills them if they have not been terminated by
* {@link TimeoutState#isSoftAborted()}.
*
* Computers are executed using a priority system, with those who have spent less time executing having a higher
* priority than those hogging the thread. This, combined with {@link TimeoutState#isPaused()} means we can reduce the
* risk of badly behaved computers stalling execution for everyone else.
*
* This is done using an implementation of Linux's Completely Fair Scheduler. When a computer executes, we compute what
* share of execution time it has used (time executed/number of tasks). We then pick the computer who has the least
* "virtual execution time" (aka {@link ComputerExecutor#virtualRuntime}).
*
* When adding a computer to the queue, we make sure its "virtual runtime" is at least as big as the smallest runtime.
* This means that adding computers which have slept a lot do not then have massive priority over everyone else. See
* {@link #queue(ComputerExecutor)} for how this is implemented.
*
* In reality, it's unlikely that more than a few computers are waiting to execute at once, so this will not have much
* effect unless you have a computer hogging execution time. However, it is pretty effective in those situations.
*
* @see TimeoutState For how hard timeouts are handled.
* @see ComputerExecutor For how computers actually do execution.
*/
public final class ComputerThread
public class ComputerThread
{
/**
* How often the computer thread monitor should run, in milliseconds
*
* @see Monitor
*/
private static final int MONITOR_WAKEUP = 100;
private static final int QUEUE_LIMIT = 256;
/**
* The target latency between executing two tasks on a single machine.
*
* An average tick takes 50ms, and so we ideally need to have handled a couple of events within that window in order
* to have a perceived low latency.
* Lock used for modifications to the object
*/
private static final long DEFAULT_LATENCY = TimeUnit.MILLISECONDS.toNanos( 50 );
private static final Object s_stateLock = new Object();
/**
* The minimum value that {@link #DEFAULT_LATENCY} can have when scaled.
*
* From statistics gathered on SwitchCraft, almost all machines will execute under 15ms, 75% under 1.5ms, with the
* mean being about 3ms. Most computers shouldn't be too impacted with having such a short period to execute in.
* Lock for various task operations
*/
private static final long DEFAULT_MIN_PERIOD = TimeUnit.MILLISECONDS.toNanos( 5 );
private static final Object s_taskLock = new Object();
/**
* The maximum number of tasks before we have to start scaling latency linearly.
* Map of objects to task list
*/
private static final long LATENCY_MAX_TASKS = DEFAULT_LATENCY / DEFAULT_MIN_PERIOD;
/**
* Lock used for modifications to the array of current threads.
*/
private static final Object threadLock = new Object();
/**
* Whether the computer thread system is currently running
*/
private static volatile boolean running = false;
/**
* The current task manager.
*/
private static Thread monitor;
/**
* The array of current runners, and their owning threads.
*/
private static TaskRunner[] runners;
private static long latency;
private static long minPeriod;
private static final ReentrantLock computerLock = new ReentrantLock();
private static final Condition hasWork = computerLock.newCondition();
private static final WeakHashMap<Object, BlockingQueue<ITask>> s_computerTaskQueues = new WeakHashMap<>();
/**
* Active queues to execute
*/
private static final TreeSet<ComputerExecutor> computerQueue = new TreeSet<>( ( a, b ) -> {
if( a == b ) return 0; // Should never happen, but let's be consistent here
long at = a.virtualRuntime, bt = b.virtualRuntime;
if( at == bt ) return Integer.compare( a.hashCode(), b.hashCode() );
return at < bt ? -1 : 1;
} );
private static final BlockingQueue<BlockingQueue<ITask>> s_computerTasksActive = new LinkedBlockingQueue<>();
private static final Set<BlockingQueue<ITask>> s_computerTasksActiveSet = new HashSet<>();
/**
* The minimum {@link ComputerExecutor#virtualRuntime} time on the tree.
* The default object for items which don't have an owner
*/
private static long minimumVirtualRuntime = 0;
private static final Object s_defaultOwner = new Object();
private static final ThreadFactory monitorFactory = ThreadUtils.factory( "Computer-Monitor" );
private static final ThreadFactory runnerFactory = ThreadUtils.factory( "Computer-Runner" );
/**
* Whether the thread is stopped or should be stopped
*/
private static boolean s_stopped = false;
private ComputerThread() {}
/**
* The thread tasks execute on
*/
private static Thread[] s_threads = null;
private static final ThreadFactory s_ManagerFactory = ThreadUtils.factory( "Computer-Manager" );
private static final ThreadFactory s_RunnerFactory = ThreadUtils.factory( "Computer-Runner" );
/**
* Start the computer thread
*/
static void start()
public static void start()
{
synchronized( threadLock )
synchronized( s_stateLock )
{
running = true;
if( runners == null )
s_stopped = false;
if( s_threads == null || s_threads.length != ComputerCraft.computer_threads )
{
// TODO: Change the runners length on config reloads
runners = new TaskRunner[ComputerCraft.computer_threads];
// latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for
// longer when executing on more than one thread.
long factor = 64 - Long.numberOfLeadingZeros( runners.length );
latency = DEFAULT_LATENCY * factor;
minPeriod = DEFAULT_MIN_PERIOD * factor;
s_threads = new Thread[ComputerCraft.computer_threads];
}
for( int i = 0; i < runners.length; i++ )
for( int i = 0; i < s_threads.length; i++ )
{
TaskRunner runner = runners[i];
if( runner == null || runner.owner == null || !runner.owner.isAlive() )
Thread thread = s_threads[i];
if( thread == null || !thread.isAlive() )
{
// Mark the old runner as dead, just in case.
if( runner != null ) runner.running = false;
// And start a new runner
runnerFactory.newThread( runners[i] = new TaskRunner() ).start();
(s_threads[i] = s_ManagerFactory.newThread( new TaskExecutor() )).start();
}
}
if( monitor == null || !monitor.isAlive() ) (monitor = monitorFactory.newThread( new Monitor() )).start();
}
}
/**
* Attempt to stop the computer thread. This interrupts each runner, and clears the task queue.
* Attempt to stop the computer thread
*/
public static void stop()
{
synchronized( threadLock )
synchronized( s_stateLock )
{
running = false;
if( runners != null )
if( s_threads != null )
{
for( TaskRunner runner : runners )
s_stopped = true;
for( Thread thread : s_threads )
{
if( runner == null ) continue;
runner.running = false;
if( runner.owner != null ) runner.owner.interrupt();
if( thread != null && thread.isAlive() )
{
thread.interrupt();
}
}
}
}
computerLock.lock();
try
synchronized( s_taskLock )
{
computerQueue.clear();
}
finally
{
computerLock.unlock();
s_computerTaskQueues.clear();
s_computerTasksActive.clear();
s_computerTasksActiveSet.clear();
}
}
/**
* Mark a computer as having work, enqueuing it on the thread
* Queue a task to execute on the thread
*
* You must be holding {@link ComputerExecutor}'s {@code queueLock} when calling this method - it should only
* be called from {@code enqueue}.
*
* @param executor The computer to execute work on.
* @param task The task to execute
* @param computer The computer to execute it on, use {@code null} to execute on the default object.
*/
static void queue( @Nonnull ComputerExecutor executor )
public static void queueTask( ITask task, Computer computer )
{
computerLock.lock();
try
Object queueObject = computer == null ? s_defaultOwner : computer;
BlockingQueue<ITask> queue;
synchronized( s_computerTaskQueues )
{
if( executor.onComputerQueue ) throw new IllegalStateException( "Cannot queue already queued executor" );
executor.onComputerQueue = true;
updateRuntimes( null );
// We're not currently on the queue, so update its current execution time to
// ensure its at least as high as the minimum.
long newRuntime = minimumVirtualRuntime;
if( executor.virtualRuntime == 0 )
queue = s_computerTaskQueues.get( queueObject );
if( queue == null )
{
// Slow down new computers a little bit.
newRuntime += scaledPeriod();
}
else
{
// Give a small boost to computers which have slept a little.
newRuntime -= latency / 2;
}
executor.virtualRuntime = Math.max( newRuntime, executor.virtualRuntime );
// Add to the queue, and signal the workers.
computerQueue.add( executor );
hasWork.signal();
}
finally
{
computerLock.unlock();
}
}
/**
* Update the {@link ComputerExecutor#virtualRuntime}s of all running tasks, and then update the
* {@link #minimumVirtualRuntime} based on the current tasks.
*
* This is called before queueing tasks, to ensure that {@link #minimumVirtualRuntime} is up-to-date.
*/
private static void updateRuntimes( @Nullable ComputerExecutor current )
{
long minRuntime = Long.MAX_VALUE;
// If we've a task on the queue, use that as our base time.
if( !computerQueue.isEmpty() ) minRuntime = computerQueue.first().virtualRuntime;
// Update all the currently executing tasks
long now = System.nanoTime();
int tasks = 1 + computerQueue.size();
TaskRunner[] currentRunners = runners;
if( currentRunners != null )
{
for( TaskRunner runner : currentRunners )
{
if( runner == null ) continue;
ComputerExecutor executor = runner.currentExecutor.get();
if( executor == null ) continue;
// We do two things here: first we update the task's virtual runtime based on when we
// last checked, and then we check the minimum.
minRuntime = Math.min( minRuntime, executor.virtualRuntime += (now - executor.vRuntimeStart) / tasks );
executor.vRuntimeStart = now;
s_computerTaskQueues.put( queueObject, queue = new LinkedBlockingQueue<>( QUEUE_LIMIT ) );
}
}
// And update the most recently executed one (if set).
if( current != null )
synchronized( s_taskLock )
{
minRuntime = Math.min( minRuntime, current.virtualRuntime += (now - current.vRuntimeStart) / tasks );
}
if( minRuntime > minimumVirtualRuntime && minRuntime < Long.MAX_VALUE )
{
minimumVirtualRuntime = minRuntime;
if( queue.offer( task ) && !s_computerTasksActiveSet.contains( queue ) )
{
s_computerTasksActive.add( queue );
s_computerTasksActiveSet.add( queue );
}
}
}
/**
* Ensure the "currently working" state of the executor is reset, the timings are updated, and then requeue the
* executor if needed.
* Responsible for pulling and managing computer tasks. This pulls a task from {@link #s_computerTasksActive},
* creates a new thread using {@link TaskRunner} or reuses a previous one and uses that to execute the task.
*
* @param runner The runner this task was on.
* @param executor The executor to requeue
* If the task times out, then it will attempt to interrupt the {@link TaskRunner} instance.
*/
private static void afterWork( TaskRunner runner, ComputerExecutor executor )
private static final class TaskExecutor implements Runnable
{
// Clear the executor's thread.
Thread currentThread = executor.executingThread.getAndSet( null );
if( currentThread != runner.owner )
{
ComputerCraft.log.error(
"Expected computer #{} to be running on {}, but already running on {}. This is a SERIOUS bug, please report with your debug.log.",
executor.getComputer().getID(), runner.owner.getName(), currentThread == null ? "nothing" : currentThread.getName()
);
}
private TaskRunner runner;
private Thread thread;
computerLock.lock();
try
{
updateRuntimes( executor );
// If we've no more tasks, just return.
if( !executor.afterWork() ) return;
// Otherwise, add to the queue, and signal any waiting workers.
computerQueue.add( executor );
hasWork.signal();
}
finally
{
computerLock.unlock();
}
}
/**
* The scaled period for a single task
*
* @return The scaled period for the task
* @see #DEFAULT_LATENCY
* @see #DEFAULT_MIN_PERIOD
* @see #LATENCY_MAX_TASKS
*/
static long scaledPeriod()
{
// +1 to include the current task
int count = 1 + computerQueue.size();
return count < LATENCY_MAX_TASKS ? latency / count : minPeriod;
}
/**
* Determine if the thread has computers queued up
*
* @return If we have work queued up.
*/
static boolean hasPendingWork()
{
return !computerQueue.isEmpty();
}
/**
* Observes all currently active {@link TaskRunner}s and terminates their tasks once they have exceeded the hard
* abort limit.
*
* @see TimeoutState
*/
private static final class Monitor implements Runnable
{
@Override
public void run()
{
@@ -360,180 +161,201 @@ public final class ComputerThread
{
while( true )
{
Thread.sleep( MONITOR_WAKEUP );
// Wait for an active queue to execute
BlockingQueue<ITask> queue = s_computerTasksActive.take();
TaskRunner[] currentRunners = ComputerThread.runners;
if( currentRunners != null )
// If threads should be stopped then return
synchronized( s_stateLock )
{
for( int i = 0; i < currentRunners.length; i++ )
{
TaskRunner runner = currentRunners[i];
// If we've no runner, skip.
if( runner == null || runner.owner == null || !runner.owner.isAlive() )
{
if( !running ) continue;
// Mark the old runner as dead and start a new one.
ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!",
runner != null && runner.owner != null ? runner.owner.getName() : runner );
if( runner != null ) runner.running = false;
runnerFactory.newThread( runners[i] = new TaskRunner() ).start();
}
// If the runner has no work, skip
ComputerExecutor executor = runner.currentExecutor.get();
if( executor == null ) continue;
// If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT),
// then we can let the Lua machine do its work.
long afterStart = executor.timeout.nanoCumulative();
long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT;
if( afterHardAbort < 0 ) continue;
// Set the hard abort flag.
executor.timeout.hardAbort();
executor.abort();
if( afterHardAbort >= ABORT_TIMEOUT )
{
// If we've hard aborted but we're still not dead, dump the stack trace and interrupt
// the task.
timeoutTask( executor, runner.owner, afterStart );
runner.owner.interrupt();
}
else if( afterHardAbort >= ABORT_TIMEOUT * 2 )
{
// If we've hard aborted and interrupted, and we're still not dead, then mark the runner
// as dead, finish off the task, and spawn a new runner.
timeoutTask( executor, runner.owner, afterStart );
runner.running = false;
runner.owner.interrupt();
ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null );
if( thisExecutor != null ) afterWork( runner, executor );
synchronized( threadLock )
{
if( running && runners.length > i && runners[i] == runner )
{
runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start();
}
}
}
}
if( s_stopped ) return;
}
execute( queue );
}
}
catch( InterruptedException ignored )
{
Thread.currentThread().interrupt();
}
}
private void execute( BlockingQueue<ITask> queue ) throws InterruptedException
{
ITask task = queue.remove();
if( thread == null || !thread.isAlive() )
{
runner = new TaskRunner();
(thread = s_RunnerFactory.newThread( runner )).start();
}
long start = System.nanoTime();
// Execute the task
runner.submit( task );
try
{
// If we timed out rather than exiting:
boolean done = runner.await( 7000 );
if( !done )
{
// Attempt to soft then hard abort
Computer computer = task.getOwner();
if( computer != null )
{
computer.abort( false );
done = runner.await( 1500 );
if( !done )
{
computer.abort( true );
done = runner.await( 1500 );
}
}
// Interrupt the thread
if( !done )
{
if( ComputerCraft.logPeripheralErrors )
{
long time = System.nanoTime() - start;
StringBuilder builder = new StringBuilder( "Terminating " );
if( computer != null )
{
builder.append( "computer #" ).append( computer.getID() );
}
else
{
builder.append( "unknown computer" );
}
{
builder.append( " due to timeout (running for " )
.append( time / 1e9 )
.append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " )
.append( thread.getName() )
.append( " is currently " )
.append( thread.getState() );
Object blocking = LockSupport.getBlocker( thread );
if( blocking != null ) builder.append( "\n on " ).append( blocking );
for( StackTraceElement element : thread.getStackTrace() )
{
builder.append( "\n at " ).append( element );
}
}
ComputerCraft.log.warn( builder.toString() );
}
thread.interrupt();
thread = null;
runner = null;
}
}
}
finally
{
long stop = System.nanoTime();
Computer computer = task.getOwner();
if( computer != null ) Tracking.addTaskTiming( computer, stop - start );
// Re-add it back onto the queue or remove it
synchronized( s_taskLock )
{
if( queue.isEmpty() )
{
s_computerTasksActiveSet.remove( queue );
}
else
{
s_computerTasksActive.add( queue );
}
}
}
}
}
/**
* Pulls tasks from the {@link #computerQueue} queue and runs them.
*
* This is responsible for running the {@link ComputerExecutor#work()}, {@link ComputerExecutor#beforeWork()} and
* {@link ComputerExecutor#afterWork()} functions. Everything else is either handled by the executor, timeout
* state or monitor.
* Responsible for the actual running of tasks. It waitin for the {@link TaskRunner#input} semaphore to be
* triggered, consumes a task and then triggers {@link TaskRunner#finished}.
*/
private static final class TaskRunner implements Runnable
{
Thread owner;
volatile boolean running = true;
final AtomicReference<ComputerExecutor> currentExecutor = new AtomicReference<>();
private final Semaphore input = new Semaphore();
private final Semaphore finished = new Semaphore();
private ITask task;
@Override
public void run()
{
owner = Thread.currentThread();
tasks:
while( running && ComputerThread.running )
try
{
// Wait for an active queue to execute
ComputerExecutor executor;
try
while( true )
{
computerLock.lockInterruptibly();
input.await();
try
{
while( computerQueue.isEmpty() ) hasWork.await();
executor = computerQueue.pollFirst();
assert executor != null : "hasWork should ensure we never receive null work";
task.execute();
}
finally
catch( RuntimeException e )
{
computerLock.unlock();
ComputerCraft.log.error( "Error running task.", e );
}
}
catch( InterruptedException ignored )
{
// If we've been interrupted, our running flag has probably been reset, so we'll
// just jump into the next iteration.
continue;
}
// If we're trying to executing some task on this computer while someone else is doing work, something
// is seriously wrong.
while( !executor.executingThread.compareAndSet( null, owner ) )
{
Thread existing = executor.executingThread.get();
if( existing != null )
{
ComputerCraft.log.error(
"Trying to run computer #{} on thread {}, but already running on {}. This is a SERIOUS bug, please report with your debug.log.",
executor.getComputer().getID(), owner.getName(), existing.getName()
);
continue tasks;
}
}
// Reset the timers
executor.beforeWork();
// And then set the current executor. It's important to do it afterwards, as otherwise we introduce
// race conditions with the monitor.
currentExecutor.set( executor );
// Execute the task
try
{
executor.work();
}
catch( Exception | LinkageError | VirtualMachineError e )
{
ComputerCraft.log.error( "Error running task on computer #" + executor.getComputer().getID(), e );
}
finally
{
ComputerExecutor thisExecutor = currentExecutor.getAndSet( null );
if( thisExecutor != null ) afterWork( this, executor );
task = null;
finished.signal();
}
}
catch( InterruptedException e )
{
ComputerCraft.log.error( "Error running task.", e );
Thread.currentThread().interrupt();
}
}
void submit( ITask task )
{
this.task = task;
input.signal();
}
boolean await( long timeout ) throws InterruptedException
{
return finished.await( timeout );
}
}
private static void timeoutTask( ComputerExecutor executor, Thread thread, long time )
/**
* A simple method to allow awaiting/providing a signal.
*
* Java does provide similar classes, but I only needed something simple.
*/
private static final class Semaphore
{
if( !ComputerCraft.logPeripheralErrors ) return;
private volatile boolean state = false;
StringBuilder builder = new StringBuilder()
.append( "Terminating computer #" ).append( executor.getComputer().getID() )
.append( " due to timeout (running for " ).append( time * 1e-9 )
.append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " )
.append( thread.getName() )
.append( " is currently " )
.append( thread.getState() );
Object blocking = LockSupport.getBlocker( thread );
if( blocking != null ) builder.append( "\n on " ).append( blocking );
for( StackTraceElement element : thread.getStackTrace() )
synchronized void signal()
{
builder.append( "\n at " ).append( element );
state = true;
notify();
}
ComputerCraft.log.warn( builder.toString() );
synchronized void await() throws InterruptedException
{
while( !state ) wait();
state = false;
}
synchronized boolean await( long timeout ) throws InterruptedException
{
if( !state )
{
wait( timeout );
if( !state ) return false;
}
state = false;
return true;
}
}
}

View File

@@ -1,312 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import java.util.Arrays;
/**
* Represents the "environment" that a {@link Computer} exists in.
*
* This handles storing and updating of peripherals and redstone.
*
* <h1>Redstone</h1>
* We holds three kinds of arrays for redstone, in normal and bundled versions:
* <ul>
* <li>{@link #internalOutput} is the redstone output which the computer has currently set. This is read on both
* threads, and written on the computer thread.</li>
* <li>{@link #externalOutput} is the redstone output currently propagated to the world. This is only read and written
* on the main thread.</li>
* <li>{@link #input} is the redstone input from external sources. This is read on both threads, and written on the main
* thread.</li>
* </ul>
*
* <h1>Peripheral</h1>
* We also keep track of peripherals. These are read on both threads, and only written on the main thread.
*/
public final class Environment implements IAPIEnvironment
{
private final Computer computer;
private boolean internalOutputChanged = false;
private final int[] internalOutput = new int[ComputerSide.COUNT];
private final int[] internalBundledOutput = new int[ComputerSide.COUNT];
private final int[] externalOutput = new int[ComputerSide.COUNT];
private final int[] externalBundledOutput = new int[ComputerSide.COUNT];
private boolean inputChanged = false;
private final int[] input = new int[ComputerSide.COUNT];
private final int[] bundledInput = new int[ComputerSide.COUNT];
private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT];
private IPeripheralChangeListener peripheralListener = null;
Environment( Computer computer )
{
this.computer = computer;
}
@Override
public int getComputerID()
{
return computer.assignID();
}
@Nonnull
@Override
public IComputerEnvironment getComputerEnvironment()
{
return computer.getComputerEnvironment();
}
@Nonnull
@Override
public IWorkMonitor getMainThreadMonitor()
{
return computer.getMainThreadMonitor();
}
@Nonnull
@Override
public Terminal getTerminal()
{
return computer.getTerminal();
}
@Override
public FileSystem getFileSystem()
{
return computer.getFileSystem();
}
@Override
public void shutdown()
{
computer.shutdown();
}
@Override
public void reboot()
{
computer.reboot();
}
@Override
public void queueEvent( String event, Object[] args )
{
computer.queueEvent( event, args );
}
@Override
public int getInput( ComputerSide side )
{
return input[side.ordinal()];
}
@Override
public int getBundledInput( ComputerSide side )
{
return bundledInput[side.ordinal()];
}
@Override
public void setOutput( ComputerSide side, int output )
{
int index = side.ordinal();
synchronized( internalOutput )
{
if( internalOutput[index] != output )
{
internalOutput[index] = output;
internalOutputChanged = true;
}
}
}
@Override
public int getOutput( ComputerSide side )
{
synchronized( internalOutput )
{
return computer.isOn() ? internalOutput[side.ordinal()] : 0;
}
}
@Override
public void setBundledOutput( ComputerSide side, int output )
{
int index = side.ordinal();
synchronized( internalOutput )
{
if( internalBundledOutput[index] != output )
{
internalBundledOutput[index] = output;
internalOutputChanged = true;
}
}
}
@Override
public int getBundledOutput( ComputerSide side )
{
synchronized( internalOutput )
{
return computer.isOn() ? internalBundledOutput[side.ordinal()] : 0;
}
}
public int getExternalRedstoneOutput( ComputerSide side )
{
return computer.isOn() ? externalOutput[side.ordinal()] : 0;
}
public int getExternalBundledRedstoneOutput( ComputerSide side )
{
return computer.isOn() ? externalBundledOutput[side.ordinal()] : 0;
}
public void setRedstoneInput( ComputerSide side, int level )
{
int index = side.ordinal();
if( input[index] != level )
{
input[index] = level;
inputChanged = true;
}
}
public void setBundledRedstoneInput( ComputerSide side, int combination )
{
int index = side.ordinal();
if( bundledInput[index] != combination )
{
bundledInput[index] = combination;
inputChanged = true;
}
}
/**
* Called on the main thread to update the internal state of the computer.
*
* This just queues a {@code redstone} event if the input has changed.
*/
void update()
{
if( inputChanged )
{
inputChanged = false;
queueEvent( "redstone", null );
}
}
/**
* Called on the main thread to propagate the internal outputs to the external ones.
*
* @return If the outputs have changed.
*/
boolean updateOutput()
{
// Mark output as changed if the internal redstone has changed
synchronized( internalOutput )
{
if( !internalOutputChanged ) return false;
boolean changed = false;
for( int i = 0; i < ComputerSide.COUNT; i++ )
{
if( externalOutput[i] != internalOutput[i] )
{
externalOutput[i] = internalOutput[i];
changed = true;
}
if( externalBundledOutput[i] != internalBundledOutput[i] )
{
externalBundledOutput[i] = internalBundledOutput[i];
changed = true;
}
}
internalOutputChanged = false;
return changed;
}
}
void resetOutput()
{
// Reset redstone output
synchronized( internalOutput )
{
Arrays.fill( internalOutput, 0 );
Arrays.fill( internalBundledOutput, 0 );
internalOutputChanged = true;
}
}
@Override
public IPeripheral getPeripheral( ComputerSide side )
{
synchronized( peripherals )
{
return peripherals[side.ordinal()];
}
}
public void setPeripheral( ComputerSide side, IPeripheral peripheral )
{
synchronized( peripherals )
{
int index = side.ordinal();
IPeripheral existing = peripherals[index];
if( (existing == null && peripheral != null) ||
(existing != null && peripheral == null) ||
(existing != null && !existing.equals( peripheral )) )
{
peripherals[index] = peripheral;
if( peripheralListener != null ) peripheralListener.onPeripheralChanged( side, peripheral );
}
}
}
@Override
public void setPeripheralChangeListener( IPeripheralChangeListener listener )
{
synchronized( peripherals )
{
peripheralListener = listener;
}
}
@Override
public String getLabel()
{
return computer.getLabel();
}
@Override
public void setLabel( String label )
{
computer.setLabel( label );
}
@Override
public void addTrackingChange( @Nonnull TrackingField field, long change )
{
Tracking.addValue( computer, field, change );
}
}

View File

@@ -0,0 +1,15 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import javax.annotation.Nullable;
public interface IComputerOwned
{
@Nullable
Computer getComputer();
}

View File

@@ -0,0 +1,14 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
public interface ITask
{
Computer getOwner();
void execute();
}

View File

@@ -6,190 +6,66 @@
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.core.tracking.Tracking;
import javax.annotation.Nonnull;
import java.util.HashSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong;
import java.util.ArrayDeque;
import java.util.Queue;
/**
* Runs tasks on the main (server) thread, ticks {@link MainThreadExecutor}s, and limits how much time is used this
* tick.
*
* Similar to {@link MainThreadExecutor}, the {@link MainThread} can be in one of three states: cool, hot and cooling.
* However, the implementation here is a little different:
*
* {@link MainThread} starts cool, and runs as many tasks as it can in the current {@link #budget}ns. Any external tasks
* (those run by tile entities, etc...) will also consume the budget
*
* Next tick, we put {@link ComputerCraft#maxMainGlobalTime} into our budget (and clamp it to that value to). If we're
* still over budget, then we should not execute <em>any</em> work (either as part of {@link MainThread} or externally).
*/
public final class MainThread
public class MainThread
{
/**
* An internal counter for {@link ILuaTask} ids.
*
* @see dan200.computercraft.api.lua.ILuaContext#issueMainThreadTask(ILuaTask)
* @see #getUniqueTaskID()
*/
private static final AtomicLong lastTaskId = new AtomicLong();
private static final int MAX_TASKS_PER_TICK = 1000;
private static final int MAX_TASKS_TOTAL = 50000;
/**
* The queue of {@link MainThreadExecutor}s with tasks to perform.
*/
private static final TreeSet<MainThreadExecutor> executors = new TreeSet<>( ( a, b ) -> {
if( a == b ) return 0; // Should never happen, but let's be consistent here
long at = a.virtualTime, bt = b.virtualTime;
if( at == bt ) return Integer.compare( a.hashCode(), b.hashCode() );
return at < bt ? -1 : 1;
} );
/**
* The set of executors which went over budget in a previous tick, and are waiting for their time to run down.
*
* @see MainThreadExecutor#tickCooling()
* @see #cooling(MainThreadExecutor)
*/
private static final HashSet<MainThreadExecutor> cooling = new HashSet<>();
/**
* The current tick number. This is used by {@link MainThreadExecutor} to determine when to reset its own time
* counter.
*
* @see #currentTick()
*/
private static int currentTick;
/**
* The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget.
*/
private static long budget;
/**
* Whether we should be executing any work this tick.
*
* This is true iff {@code MAX_TICK_TIME - currentTime} was true <em>at the beginning of the tick</em>.
*/
private static boolean canExecute = true;
private static long minimumTime = 0;
private MainThread() {}
private static final Queue<ITask> m_outstandingTasks = new ArrayDeque<>();
private static final Object m_nextUnusedTaskIDLock = new Object();
private static long m_nextUnusedTaskID = 0;
public static long getUniqueTaskID()
{
return lastTaskId.incrementAndGet();
}
static void queue( @Nonnull MainThreadExecutor executor, boolean sleeper )
{
synchronized( executors )
synchronized( m_nextUnusedTaskIDLock )
{
if( executor.onQueue ) throw new IllegalStateException( "Cannot queue already queued executor" );
executor.onQueue = true;
executor.updateTime();
// We're not currently on the queue, so update its current execution time to
// ensure its at least as high as the minimum.
long newRuntime = minimumTime;
// Slow down new computers a little bit.
if( executor.virtualTime == 0 ) newRuntime += ComputerCraft.maxMainComputerTime;
executor.virtualTime = Math.max( newRuntime, executor.virtualTime );
executors.add( executor );
return ++m_nextUnusedTaskID;
}
}
static void cooling( @Nonnull MainThreadExecutor executor )
public static boolean queueTask( ITask task )
{
cooling.add( executor );
}
static void consumeTime( long time )
{
budget -= time;
}
static boolean canExecute()
{
return canExecute;
}
static int currentTick()
{
return currentTick;
synchronized( m_outstandingTasks )
{
if( m_outstandingTasks.size() < MAX_TASKS_TOTAL )
{
m_outstandingTasks.offer( task );
return true;
}
}
return false;
}
public static void executePendingTasks()
{
// Move onto the next tick and cool down the global executor. We're allowed to execute if we have _any_ time
// allocated for this tick. This means we'll stick much closer to doing MAX_TICK_TIME work every tick.
//
// Of course, we'll go over the MAX_TICK_TIME most of the time, but eventually that overrun will accumulate
// and we'll skip a whole tick - bringing the average back down again.
currentTick++;
budget = Math.min( budget + ComputerCraft.maxMainGlobalTime, ComputerCraft.maxMainGlobalTime );
canExecute = budget > 0;
// Cool down any warm computers.
cooling.removeIf( MainThreadExecutor::tickCooling );
if( !canExecute ) return;
// Run until we meet the deadline.
long start = System.nanoTime();
long deadline = start + budget;
while( true )
int tasksThisTick = 0;
while( tasksThisTick < MAX_TASKS_PER_TICK )
{
MainThreadExecutor executor;
synchronized( executors )
ITask task = null;
synchronized( m_outstandingTasks )
{
executor = executors.pollFirst();
task = m_outstandingTasks.poll();
}
if( executor == null ) break;
long taskStart = System.nanoTime();
executor.execute();
long taskStop = System.nanoTime();
synchronized( executors )
if( task != null )
{
if( executor.afterExecute( taskStop - taskStart ) ) executors.add( executor );
long start = System.nanoTime();
task.execute();
// Compute the new minimum time (including the next task on the queue too). Note that this may also include
// time spent in external tasks.
long newMinimum = executor.virtualTime;
if( !executors.isEmpty() )
{
MainThreadExecutor next = executors.first();
if( next.virtualTime < newMinimum ) newMinimum = next.virtualTime;
}
minimumTime = Math.max( minimumTime, newMinimum );
long stop = System.nanoTime();
Computer computer = task.getOwner();
if( computer != null ) Tracking.addServerTiming( computer, stop - start );
++tasksThisTick;
}
else
{
break;
}
if( taskStop >= deadline ) break;
}
consumeTime( System.nanoTime() - start );
}
public static void reset()
{
currentTick = 0;
budget = 0;
canExecute = true;
minimumTime = 0;
lastTaskId.set( 0 );
cooling.clear();
synchronized( executors )
{
executors.clear();
}
}
}

View File

@@ -1,250 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.turtle.core.TurtleBrain;
import net.minecraft.tileentity.TileEntity;
import javax.annotation.Nonnull;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
/**
* Keeps track of tasks that a {@link Computer} should run on the main thread and how long that has been spent executing
* them.
*
* This provides rate-limiting mechanism for tasks enqueued with {@link Computer#queueMainThread(Runnable)}, but also
* those run elsewhere (such as during the turtle's tick - see {@link TurtleBrain#update()}). In order to handle this,
* the executor goes through three stages:
*
* When {@link State#COOL}, the computer is allocated {@link ComputerCraft#maxMainComputerTime}ns to execute any work
* this tick. At the beginning of the tick, we execute as many {@link MainThread} tasks as possible, until our
* time-frame or the global time frame has expired.
*
* Then, when other objects (such as {@link TileEntity}) are ticked, we update how much time we've used using
* {@link IWorkMonitor#trackWork(long, TimeUnit)}.
*
* Now, if anywhere during this period, we use more than our allocated time slice, the executor is marked as
* {@link State#HOT}. This means it will no longer be able to execute {@link MainThread} tasks (though will still
* execute tile entity tasks, in order to prevent the main thread from exhausting work every tick).
*
* At the beginning of the next tick, we increment the budget e by {@link ComputerCraft#maxMainComputerTime} and any
* {@link State#HOT} executors are marked as {@link State#COOLING}. They will remain cooling until their budget is
* fully replenished (is equal to {@link ComputerCraft#maxMainComputerTime}). Note, this is different to
* {@link MainThread}, which allows running when it has any budget left. When cooling, <em>no</em> tasks are executed -
* be they on the tile entity or main thread.
*
* This mechanism means that, on average, computers will use at most {@link ComputerCraft#maxMainComputerTime}ns per
* second, but one task source will not prevent others from executing.
*
* @see MainThread
* @see IWorkMonitor
* @see Computer#getMainThreadMonitor()
* @see Computer#queueMainThread(Runnable)
*/
final class MainThreadExecutor implements IWorkMonitor
{
/**
* The maximum number of {@link MainThread} tasks allowed on the queue.
*/
private static final int MAX_TASKS = 5000;
private final Computer computer;
/**
* A lock used for any changes to {@link #tasks}, or {@link #onQueue}. This will be
* used on the main thread, so locks should be kept as brief as possible.
*/
private final Object queueLock = new Object();
/**
* The queue of tasks which should be executed.
*
* @see #queueLock
*/
private final Queue<Runnable> tasks = new ArrayDeque<>( 4 );
/**
* Determines if this executor is currently present on the queue.
*
* This should be true iff {@link #tasks} is non-empty.
*
* @see #queueLock
* @see #enqueue(Runnable)
* @see #afterExecute(long)
*/
volatile boolean onQueue;
/**
* The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget.
*
* @see #tickCooling()
* @see #consumeTime(long)
*/
private long budget = 0;
/**
* The last tick that {@link #budget} was updated.
*
* @see #tickCooling()
* @see #consumeTime(long)
*/
private int currentTick = -1;
/**
* The current state of this executor.
*
* @see #canWork()
*/
private State state = State.COOL;
private long pendingTime;
long virtualTime;
MainThreadExecutor( Computer computer )
{
this.computer = computer;
}
/**
* Push a task onto this executor's queue, pushing it onto the {@link MainThread} if needed.
*
* @param runnable The task to run on the main thread.
* @return Whether this task was enqueued (namely, was there space).
*/
boolean enqueue( Runnable runnable )
{
synchronized( queueLock )
{
if( tasks.size() >= MAX_TASKS || !tasks.offer( runnable ) ) return false;
if( !onQueue && state == State.COOL ) MainThread.queue( this, true );
return true;
}
}
void execute()
{
if( state != State.COOL ) return;
Runnable task;
synchronized( queueLock )
{
task = tasks.poll();
}
if( task != null ) task.run();
}
/**
* Update the time taken to run an {@link #enqueue(Runnable)} task.
*
* @param time The time some task took to run.
* @return Whether this should be added back to the queue.
*/
boolean afterExecute( long time )
{
consumeTime( time );
synchronized( queueLock )
{
virtualTime += time;
updateTime();
if( state != State.COOL || tasks.isEmpty() ) return onQueue = false;
return true;
}
}
/**
* Whether we should execute "external" tasks (ones not part of {@link #tasks}).
*
* @return Whether we can execute external tasks.
*/
@Override
public boolean canWork()
{
return state != State.COOLING && MainThread.canExecute();
}
@Override
public boolean shouldWork()
{
return state == State.COOL && MainThread.canExecute();
}
@Override
public void trackWork( long time, @Nonnull TimeUnit unit )
{
long nanoTime = unit.toNanos( time );
synchronized( queueLock )
{
pendingTime += nanoTime;
}
consumeTime( nanoTime );
MainThread.consumeTime( nanoTime );
}
private void consumeTime( long time )
{
Tracking.addServerTiming( computer, time );
// Reset the budget if moving onto a new tick. We know this is safe, as this will only have happened if
// #tickCooling() isn't called, and so we didn't overrun the previous tick.
if( currentTick != MainThread.currentTick() )
{
currentTick = MainThread.currentTick();
budget = ComputerCraft.maxMainComputerTime;
}
budget -= time;
// If we've gone over our limit, mark us as having to cool down.
if( budget < 0 && state == State.COOL )
{
state = State.HOT;
MainThread.cooling( this );
}
}
/**
* Move this executor forward one tick, replenishing the budget by {@link ComputerCraft#maxMainComputerTime}.
*
* @return Whether this executor has cooled down, and so is safe to run again.
*/
boolean tickCooling()
{
state = State.COOLING;
currentTick = MainThread.currentTick();
budget = Math.min( budget + ComputerCraft.maxMainComputerTime, ComputerCraft.maxMainComputerTime );
if( budget < ComputerCraft.maxMainComputerTime ) return false;
state = State.COOL;
synchronized( queueLock )
{
if( !tasks.isEmpty() && !onQueue ) MainThread.queue( this, false );
}
return true;
}
void updateTime()
{
virtualTime += pendingTime;
pendingTime = 0;
}
private enum State
{
COOL,
HOT,
COOLING,
}
}

View File

@@ -1,168 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.lua.MachineResult;
import java.util.concurrent.TimeUnit;
/**
* Used to measure how long a computer has executed for, and thus the relevant timeout states.
*
* Timeouts are mostly used for execution of Lua code: we should ideally never have a state where constructing the APIs
* or machines themselves takes more than a fraction of a second.
*
* When a computer runs, it is allowed to run for 7 seconds ({@link #TIMEOUT}). After that point, the "soft abort" flag
* is set ({@link #isSoftAborted()}). Here, the Lua machine will attempt to abort the program in some safe manner
* (namely, throwing a "Too long without yielding" error).
*
* Now, if a computer still does not stop after that period, they're behaving really badly. 1.5 seconds after a soft
* abort ({@link #ABORT_TIMEOUT}), we trigger a hard abort (note, this is done from the computer thread manager). This
* will destroy the entire Lua runtime and shut the computer down.
*
* The Lua runtime is also allowed to pause execution if there are other computers contesting for work. All computers
* are allowed to run for {@link ComputerThread#scaledPeriod()} nanoseconds (see {@link #currentDeadline}). After that
* period, if any computers are waiting to be executed then we'll set the paused flag to true ({@link #isPaused()}.
*
* @see ComputerThread
* @see ILuaMachine
* @see MachineResult#isPause()
*/
public final class TimeoutState
{
/**
* The total time a task is allowed to run before aborting in nanoseconds
*/
static final long TIMEOUT = TimeUnit.MILLISECONDS.toNanos( 7000 );
/**
* The time the task is allowed to run after each abort in nanoseconds
*/
static final long ABORT_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( 1500 );
/**
* The error message to display when we trigger an abort.
*/
public static final String ABORT_MESSAGE = "Too long without yielding";
private boolean paused;
private boolean softAbort;
private volatile boolean hardAbort;
/**
* When the cumulative time would have started had the whole event been processed in one go.
*/
private long cumulativeStart;
/**
* How much cumulative time has elapsed. This is effectively {@code cumulativeStart - currentStart}.
*/
private long cumulativeElapsed;
/**
* When this execution round started.
*/
private long currentStart;
/**
* When this execution round should look potentially be paused.
*/
private long currentDeadline;
long nanoCumulative()
{
return System.nanoTime() - cumulativeStart;
}
long nanoCurrent()
{
return System.nanoTime() - currentStart;
}
/**
* Recompute the {@link #isSoftAborted()} and {@link #isPaused()} flags.
*/
public void refresh()
{
// Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we
// need to handle overflow.
long now = System.nanoTime();
if( !paused ) paused = currentDeadline - now <= 0 && ComputerThread.hasPendingWork(); // now >= currentDeadline
if( !softAbort ) softAbort = now - cumulativeStart - TIMEOUT >= 0; // now - cumulativeStart >= TIMEOUT
}
/**
* Whether we should pause execution of this machine.
*
* This is determined by whether we've consumed our time slice, and if there are other computers waiting to perform
* work.
*
* @return Whether we should pause execution.
*/
public boolean isPaused()
{
return paused;
}
/**
* If the machine should be passively aborted.
*/
public boolean isSoftAborted()
{
return softAbort;
}
/**
* If the machine should be forcibly aborted.
*/
public boolean isHardAborted()
{
return hardAbort;
}
/**
* If the machine should be forcibly aborted.
*/
void hardAbort()
{
softAbort = hardAbort = true;
}
/**
* Start the current and cumulative timers again.
*/
void startTimer()
{
long now = System.nanoTime();
currentStart = now;
currentDeadline = now + ComputerThread.scaledPeriod();
// Compute the "nominal start time".
cumulativeStart = now - cumulativeElapsed;
}
/**
* Pauses the cumulative time, to be resumed by {@link #startTimer()}
*
* @see #nanoCumulative()
*/
void pauseTimer()
{
// We set the cumulative time to difference between current time and "nominal start time".
cumulativeElapsed = System.nanoTime() - cumulativeStart;
paused = false;
}
/**
* Resets the cumulative time and resets the abort flags.
*/
void stopTimer()
{
cumulativeElapsed = 0;
paused = softAbort = hardAbort = false;
}
}

View File

@@ -1,50 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.Channel;
/**
* Wraps some closeable object such as a buffered writer, and the underlying stream.
*
* When flushing a buffer before closing, some implementations will not close the buffer if an exception is thrown
* this causes us to release the channel, but not actually close it. This wrapper will attempt to close the wrapper (and
* so hopefully flush the channel), and then close the underlying channel.
*
* @param <T> The type of the closeable object to write.
*/
class ChannelWrapper<T extends Closeable> implements Closeable
{
private final T wrapper;
private final Channel channel;
ChannelWrapper( T wrapper, Channel channel )
{
this.wrapper = wrapper;
this.channel = channel;
}
@Override
public void close() throws IOException
{
try
{
wrapper.close();
}
finally
{
channel.close();
}
}
public T get()
{
return wrapper;
}
}

View File

@@ -55,8 +55,14 @@ public class FileMount implements IWritableMount
m_ignoredBytesLeft = 0;
long bytesLeft = m_capacity - m_usedSpace;
if( newBytes > bytesLeft ) throw new IOException( "Out of space" );
m_usedSpace += newBytes;
if( newBytes > bytesLeft )
{
throw new IOException( "Out of space" );
}
else
{
m_usedSpace += newBytes;
}
}
}
@@ -80,17 +86,14 @@ public class FileMount implements IWritableMount
SeekableCountingChannel( SeekableByteChannel inner, long bytesToIgnore )
{
super( inner, bytesToIgnore );
m_inner = inner;
this.m_inner = inner;
}
@Override
public SeekableByteChannel position( long newPosition ) throws IOException
{
if( !isOpen() ) throw new ClosedChannelException();
if( newPosition < 0 )
{
throw new IllegalArgumentException( "Cannot seek before the beginning of the stream" );
}
if( newPosition < 0 ) throw new IllegalArgumentException();
long delta = newPosition - m_inner.position();
if( delta < 0 )
@@ -112,7 +115,7 @@ public class FileMount implements IWritableMount
}
@Override
public int read( ByteBuffer dst ) throws ClosedChannelException
public int read( ByteBuffer dst ) throws IOException
{
if( !m_inner.isOpen() ) throw new ClosedChannelException();
throw new NonReadableChannelException();
@@ -147,19 +150,29 @@ public class FileMount implements IWritableMount
@Override
public boolean exists( @Nonnull String path )
{
if( !created() ) return path.isEmpty();
File file = getRealPath( path );
return file.exists();
if( !created() )
{
return path.length() == 0;
}
else
{
File file = getRealPath( path );
return file.exists();
}
}
@Override
public boolean isDirectory( @Nonnull String path )
{
if( !created() ) return path.isEmpty();
File file = getRealPath( path );
return file.exists() && file.isDirectory();
if( !created() )
{
return path.length() == 0;
}
else
{
File file = getRealPath( path );
return file.exists() && file.isDirectory();
}
}
@Override
@@ -167,17 +180,29 @@ public class FileMount implements IWritableMount
{
if( !created() )
{
if( !path.isEmpty() ) throw new IOException( "/" + path + ": Not a directory" );
return;
if( path.length() != 0 )
{
throw new IOException( "/" + path + ": Not a directory" );
}
}
File file = getRealPath( path );
if( !file.exists() || !file.isDirectory() ) throw new IOException( "/" + path + ": Not a directory" );
String[] paths = file.list();
for( String subPath : paths )
else
{
if( new File( file, subPath ).exists() ) contents.add( subPath );
File file = getRealPath( path );
if( file.exists() && file.isDirectory() )
{
String[] paths = file.list();
for( String subPath : paths )
{
if( new File( file, subPath ).exists() )
{
contents.add( subPath );
}
}
}
else
{
throw new IOException( "/" + path + ": Not a directory" );
}
}
}
@@ -186,14 +211,26 @@ public class FileMount implements IWritableMount
{
if( !created() )
{
if( path.isEmpty() ) return 0;
if( path.length() == 0 )
{
return 0;
}
}
else
{
File file = getRealPath( path );
if( file.exists() ) return file.isDirectory() ? 0 : file.length();
if( file.exists() )
{
if( file.isDirectory() )
{
return 0;
}
else
{
return file.length();
}
}
}
throw new IOException( "/" + path + ": No such file" );
}
@@ -205,9 +242,11 @@ public class FileMount implements IWritableMount
if( created() )
{
File file = getRealPath( path );
if( file.exists() && !file.isDirectory() ) return new FileInputStream( file );
if( file.exists() && !file.isDirectory() )
{
return new FileInputStream( file );
}
}
throw new IOException( "/" + path + ": No such file" );
}
@@ -218,9 +257,11 @@ public class FileMount implements IWritableMount
if( created() )
{
File file = getRealPath( path );
if( file.exists() && !file.isDirectory() ) return FileChannel.open( file.toPath(), READ_OPTIONS );
if( file.exists() && !file.isDirectory() )
{
return FileChannel.open( file.toPath(), READ_OPTIONS );
}
}
throw new IOException( "/" + path + ": No such file" );
}
@@ -233,42 +274,53 @@ public class FileMount implements IWritableMount
File file = getRealPath( path );
if( file.exists() )
{
if( !file.isDirectory() ) throw new IOException( "/" + path + ": File exists" );
return;
}
int dirsToCreate = 1;
File parent = file.getParentFile();
while( !parent.exists() )
{
++dirsToCreate;
parent = parent.getParentFile();
}
if( getRemainingSpace() < dirsToCreate * MINIMUM_FILE_SIZE )
{
throw new IOException( "/" + path + ": Out of space" );
}
if( file.mkdirs() )
{
m_usedSpace += dirsToCreate * MINIMUM_FILE_SIZE;
if( !file.isDirectory() )
{
throw new IOException( "/" + path + ": File exists" );
}
}
else
{
throw new IOException( "/" + path + ": Access denied" );
int dirsToCreate = 1;
File parent = file.getParentFile();
while( !parent.exists() )
{
++dirsToCreate;
parent = parent.getParentFile();
}
if( getRemainingSpace() < dirsToCreate * MINIMUM_FILE_SIZE )
{
throw new IOException( "/" + path + ": Out of space" );
}
boolean success = file.mkdirs();
if( success )
{
m_usedSpace += dirsToCreate * MINIMUM_FILE_SIZE;
}
else
{
throw new IOException( "/" + path + ": Access denied" );
}
}
}
@Override
public void delete( @Nonnull String path ) throws IOException
{
if( path.isEmpty() ) throw new IOException( "/" + path + ": Access denied" );
if( path.length() == 0 )
{
throw new IOException( "/" + path + ": Access denied" );
}
if( created() )
{
File file = getRealPath( path );
if( file.exists() ) deleteRecursively( file );
if( file.exists() )
{
deleteRecursively( file );
}
}
}
@@ -319,39 +371,60 @@ public class FileMount implements IWritableMount
{
create();
File file = getRealPath( path );
if( file.exists() && file.isDirectory() ) throw new IOException( "/" + path + ": Cannot write to directory" );
if( file.exists() )
if( file.exists() && file.isDirectory() )
{
m_usedSpace -= Math.max( file.length(), MINIMUM_FILE_SIZE );
throw new IOException( "/" + path + ": Cannot write to directory" );
}
else if( getRemainingSpace() < MINIMUM_FILE_SIZE )
else
{
throw new IOException( "/" + path + ": Out of space" );
if( !file.exists() )
{
if( getRemainingSpace() < MINIMUM_FILE_SIZE )
{
throw new IOException( "/" + path + ": Out of space" );
}
else
{
m_usedSpace += MINIMUM_FILE_SIZE;
}
}
else
{
m_usedSpace -= Math.max( file.length(), MINIMUM_FILE_SIZE );
m_usedSpace += MINIMUM_FILE_SIZE;
}
return new SeekableCountingChannel( Files.newByteChannel( file.toPath(), WRITE_OPTIONS ), MINIMUM_FILE_SIZE );
}
m_usedSpace += MINIMUM_FILE_SIZE;
return new SeekableCountingChannel( Files.newByteChannel( file.toPath(), WRITE_OPTIONS ), MINIMUM_FILE_SIZE );
}
@Nonnull
@Override
public WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
{
if( !created() )
if( created() )
{
File file = getRealPath( path );
if( !file.exists() )
{
throw new IOException( "/" + path + ": No such file" );
}
else if( file.isDirectory() )
{
throw new IOException( "/" + path + ": Cannot write to directory" );
}
else
{
// Allowing seeking when appending is not recommended, so we use a separate channel.
return new WritableCountingChannel(
Files.newByteChannel( file.toPath(), APPEND_OPTIONS ),
Math.max( MINIMUM_FILE_SIZE - file.length(), 0 )
);
}
}
else
{
throw new IOException( "/" + path + ": No such file" );
}
File file = getRealPath( path );
if( !file.exists() ) throw new IOException( "/" + path + ": No such file" );
if( file.isDirectory() ) throw new IOException( "/" + path + ": Cannot write to directory" );
// Allowing seeking when appending is not recommended, so we use a separate channel.
return new WritableCountingChannel(
Files.newByteChannel( file.toPath(), APPEND_OPTIONS ),
Math.max( MINIMUM_FILE_SIZE - file.length(), 0 )
);
}
@Override
@@ -360,7 +433,7 @@ public class FileMount implements IWritableMount
return Math.max( m_capacity - m_usedSpace, 0 );
}
private File getRealPath( String path )
public File getRealPath( String path )
{
return new File( m_rootPath, path );
}
@@ -382,10 +455,12 @@ public class FileMount implements IWritableMount
}
}
private static long measureUsedSpace( File file )
private long measureUsedSpace( File file )
{
if( !file.exists() ) return 0;
if( !file.exists() )
{
return 0;
}
if( file.isDirectory() )
{
long size = MINIMUM_FILE_SIZE;

View File

@@ -19,7 +19,6 @@ import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.AccessDeniedException;
@@ -29,7 +28,7 @@ import java.util.regex.Pattern;
public class FileSystem
{
private static class MountWrapper
private class MountWrapper
{
private String m_label;
private String m_location;
@@ -37,7 +36,7 @@ public class FileSystem
private IMount m_mount;
private IWritableMount m_writableMount;
MountWrapper( String label, String location, IMount mount )
public MountWrapper( String label, String location, IMount mount )
{
m_label = label;
m_location = location;
@@ -80,7 +79,7 @@ public class FileSystem
public boolean isReadOnly( String path )
{
return m_writableMount == null;
return (m_writableMount == null);
}
// IMount forwarders:
@@ -318,7 +317,7 @@ public class FileSystem
private final FileSystemWrapperMount m_wrapper = new FileSystemWrapperMount( this );
private final Map<String, MountWrapper> m_mounts = new HashMap<>();
private final HashMap<WeakReference<FileSystemWrapper<?>>, ChannelWrapper<?>> m_openFiles = new HashMap<>();
private final HashMap<WeakReference<FileSystemWrapper<?>>, Closeable> m_openFiles = new HashMap<>();
private final ReferenceQueue<FileSystemWrapper<?>> m_openFileQueue = new ReferenceQueue<>();
public FileSystem( String rootLabel, IMount rootMount ) throws FileSystemException
@@ -331,7 +330,7 @@ public class FileSystem
mountWritable( rootLabel, "", rootMount );
}
public void close()
public void unload()
{
// Close all dangling open files
synchronized( m_openFiles )
@@ -474,7 +473,7 @@ public class FileSystem
String[] list = list( dir );
for( String entry : list )
{
String entryPath = dir.isEmpty() ? entry : dir + "/" + entry;
String entryPath = dir.isEmpty() ? entry : (dir + "/" + entry);
if( wildPattern.matcher( entryPath ).matches() )
{
matches.add( entryPath );
@@ -662,7 +661,7 @@ public class FileSystem
}
}
private synchronized <T extends Closeable> FileSystemWrapper<T> openFile( @Nonnull Channel channel, @Nonnull T file ) throws FileSystemException
private synchronized <T extends Closeable> FileSystemWrapper<T> openFile( @Nonnull T file ) throws FileSystemException
{
synchronized( m_openFiles )
{
@@ -670,14 +669,12 @@ public class FileSystem
m_openFiles.size() >= ComputerCraft.maximumFilesOpen )
{
IoUtil.closeQuietly( file );
IoUtil.closeQuietly( channel );
throw new FileSystemException( "Too many files already open" );
}
ChannelWrapper<T> channelWrapper = new ChannelWrapper<>( file, channel );
FileSystemWrapper<T> fsWrapper = new FileSystemWrapper<>( this, channelWrapper, m_openFileQueue );
m_openFiles.put( fsWrapper.self, channelWrapper );
return fsWrapper;
FileSystemWrapper<T> wrapper = new FileSystemWrapper<>( this, file, m_openFileQueue );
m_openFiles.put( wrapper.self, file );
return wrapper;
}
}
@@ -698,7 +695,7 @@ public class FileSystem
ReadableByteChannel channel = mount.openForRead( path );
if( channel != null )
{
return openFile( channel, open.apply( channel ) );
return openFile( open.apply( channel ) );
}
return null;
}
@@ -712,7 +709,7 @@ public class FileSystem
WritableByteChannel channel = append ? mount.openForAppend( path ) : mount.openForWrite( path );
if( channel != null )
{
return openFile( channel, open.apply( channel ) );
return openFile( open.apply( channel ) );
}
return null;
}
@@ -768,7 +765,7 @@ public class FileSystem
path = path.replace( '\\', '/' );
// Clean the path or illegal characters.
final char[] specialChars = new char[] {
final char[] specialChars = {
'"', ':', '<', '>', '?', '|' // Sorted by ascii value (important)
};
@@ -788,14 +785,13 @@ public class FileSystem
Stack<String> outputParts = new Stack<>();
for( String part : parts )
{
if( part.isEmpty() || part.equals( "." ) || threeDotsPattern.matcher( part ).matches() )
if( part.length() == 0 || part.equals( "." ) || threeDotsPattern.matcher( part ).matches() )
{
// . is redundant
// ... and more are treated as .
continue;
}
if( part.equals( ".." ) )
else if( part.equals( ".." ) )
{
// .. can cancel out the last folder entered
if( !outputParts.empty() )
@@ -828,7 +824,7 @@ public class FileSystem
}
// Recombine the output parts into a new string
StringBuilder result = new StringBuilder();
StringBuilder result = new StringBuilder( "" );
Iterator<String> it = outputParts.iterator();
while( it.hasNext() )
{
@@ -875,7 +871,7 @@ public class FileSystem
path = sanitizePath( path );
location = sanitizePath( location );
assert contains( location, path );
assert (contains( location, path ));
String local = path.substring( location.length() );
if( local.startsWith( "/" ) )
{

View File

@@ -15,27 +15,22 @@ import java.lang.ref.WeakReference;
/**
* An alternative closeable implementation that will free up resources in the filesystem.
*
* The {@link FileSystem} maps weak references of this to its underlying object. If the wrapper has been disposed of
* (say, the Lua object referencing it has gone), then the wrapped object will be closed by the filesystem.
*
* Closing this will stop the filesystem tracking it, reducing the current descriptor count.
*
* In an ideal world, we'd just wrap the closeable. However, as we do some {@code instanceof} checks
* on the stream, it's not really possible as it'd require numerous instances.
*
* @param <T> The type of writer or channel to wrap.
* @param <T> The stream to wrap.
*/
public class FileSystemWrapper<T extends Closeable> implements Closeable
{
private final FileSystem fileSystem;
private final ChannelWrapper<T> closeable;
private final T closeable;
final WeakReference<FileSystemWrapper<?>> self;
FileSystemWrapper( FileSystem fileSystem, ChannelWrapper<T> closeable, ReferenceQueue<FileSystemWrapper<?>> queue )
FileSystemWrapper( FileSystem fileSystem, T closeable, ReferenceQueue<FileSystemWrapper<?>> queue )
{
this.fileSystem = fileSystem;
this.closeable = closeable;
self = new WeakReference<>( this, queue );
this.self = new WeakReference<>( this, queue );
}
@Override
@@ -48,6 +43,6 @@ public class FileSystemWrapper<T extends Closeable> implements Closeable
@Nonnull
public T get()
{
return closeable.get();
return closeable;
}
}

View File

@@ -23,9 +23,9 @@ public class FileSystemWrapperMount implements IFileSystem
{
private final FileSystem m_filesystem;
public FileSystemWrapperMount( FileSystem filesystem )
public FileSystemWrapperMount( FileSystem m_filesystem )
{
this.m_filesystem = filesystem;
this.m_filesystem = m_filesystem;
}
@Override

View File

@@ -69,23 +69,23 @@ public class JarMount implements IMount
// Cleanup any old mounts. It's unlikely that there will be any, but it's best to be safe.
cleanup();
if( !jarFile.exists() || jarFile.isDirectory() ) throw new FileNotFoundException( "Cannot find " + jarFile );
if( !jarFile.exists() || jarFile.isDirectory() ) throw new FileNotFoundException();
// Open the zip file
try
{
zip = new ZipFile( jarFile );
}
catch( IOException e )
catch( Exception e )
{
throw new IOException( "Error loading zip file", e );
throw new IOException( "Error loading zip file" );
}
// Ensure the root entry exists.
if( zip.getEntry( subPath ) == null )
{
zip.close();
throw new FileNotFoundException( "Zip does not contain path" );
throw new IOException( "Zip does not contain path" );
}
// We now create a weak reference to this mount. This is automatically added to the appropriate queue.
@@ -212,7 +212,7 @@ public class JarMount implements IMount
}
}
}
catch( IOException e )
catch( Exception e )
{
// Treat errors as non-existence of file
}

View File

@@ -68,7 +68,7 @@ public class SubMount implements IMount
private String getFullPath( String path )
{
if( path.isEmpty() )
if( path.length() == 0 )
{
return m_subPath;
}

View File

@@ -9,8 +9,8 @@ package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.computer.TimeoutState;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.util.ThreadUtils;
@@ -20,13 +20,16 @@ import org.squiddev.cobalt.compiler.LoadState;
import org.squiddev.cobalt.debug.DebugFrame;
import org.squiddev.cobalt.debug.DebugHandler;
import org.squiddev.cobalt.debug.DebugState;
import org.squiddev.cobalt.function.LibFunction;
import org.squiddev.cobalt.function.LuaFunction;
import org.squiddev.cobalt.function.VarArgFunction;
import org.squiddev.cobalt.lib.*;
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
@@ -35,14 +38,13 @@ import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static org.squiddev.cobalt.Constants.NONE;
import static org.squiddev.cobalt.ValueFactory.valueOf;
import static org.squiddev.cobalt.ValueFactory.varargsOf;
import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKED;
import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKYIELD;
public class CobaltLuaMachine implements ILuaMachine
{
private static final ThreadPoolExecutor COROUTINES = new ThreadPoolExecutor(
private static final ThreadPoolExecutor coroutines = new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
5L, TimeUnit.MINUTES,
new SynchronousQueue<>(),
@@ -50,29 +52,74 @@ public class CobaltLuaMachine implements ILuaMachine
);
private final Computer m_computer;
private final TimeoutState timeout;
private final TimeoutDebugHandler debug;
private final ILuaContext context = new CobaltLuaContext();
private LuaState m_state;
private LuaTable m_globals;
private LuaThread m_mainRoutine;
private LuaThread m_mainRoutine = null;
private String m_eventFilter = null;
private String m_eventFilter;
private String m_softAbortMessage;
private String m_hardAbortMessage;
public CobaltLuaMachine( Computer computer, TimeoutState timeout )
public CobaltLuaMachine( Computer computer )
{
m_computer = computer;
this.timeout = timeout;
debug = new TimeoutDebugHandler();
// Create an environment to run in
LuaState state = m_state = LuaState.builder()
LuaState state = this.m_state = LuaState.builder()
.resourceManipulator( new VoidResourceManipulator() )
.debug( debug )
.coroutineExecutor( command -> {
.debug( new DebugHandler()
{
private int count = 0;
private boolean hasSoftAbort;
@Override
public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable
{
int count = ++this.count;
if( count > 100000 )
{
if( m_hardAbortMessage != null ) throw HardAbortError.INSTANCE;
this.count = 0;
}
else
{
handleSoftAbort();
}
super.onInstruction( ds, di, pc );
}
@Override
public void poll() throws LuaError
{
if( m_hardAbortMessage != null ) throw HardAbortError.INSTANCE;
handleSoftAbort();
}
private void handleSoftAbort() throws LuaError
{
// If the soft abort has been cleared then we can reset our flags and continue.
String message = m_softAbortMessage;
if( message == null )
{
hasSoftAbort = false;
return;
}
if( hasSoftAbort && m_hardAbortMessage == null )
{
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
return;
}
hasSoftAbort = true;
throw new LuaError( message );
}
} )
.yieldThreader( command -> {
Tracking.addValue( m_computer, TrackingField.COROUTINES_CREATED, 1 );
COROUTINES.execute( () -> {
coroutines.execute( () -> {
try
{
command.run();
@@ -97,6 +144,9 @@ public class CobaltLuaMachine implements ILuaMachine
m_globals.load( state, new Bit32Lib() );
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
// Register custom load/loadstring provider which automatically adds prefixes.
LibFunction.bind( m_globals, PrefixLoader.class, new String[] { "load", "loadstring" } );
// Remove globals we don't want to expose
m_globals.rawset( "collectgarbage", Constants.NIL );
m_globals.rawset( "dofile", Constants.NIL );
@@ -111,10 +161,17 @@ public class CobaltLuaMachine implements ILuaMachine
{
m_globals.rawset( "_CC_DISABLE_LUA51_FEATURES", Constants.TRUE );
}
// Our main function will go here
m_mainRoutine = null;
m_eventFilter = null;
m_softAbortMessage = null;
m_hardAbortMessage = null;
}
@Override
public void addAPI( @Nonnull ILuaAPI api )
public void addAPI( ILuaAPI api )
{
// Add the methods of an API to the global table
LuaTable table = wrapLuaObject( api );
@@ -126,44 +183,37 @@ public class CobaltLuaMachine implements ILuaMachine
}
@Override
public MachineResult loadBios( @Nonnull InputStream bios )
public void loadBios( InputStream bios )
{
// Begin executing a file (ie, the bios)
if( m_mainRoutine != null ) return MachineResult.OK;
if( m_mainRoutine != null ) return;
try
{
LuaFunction value = LoadState.load( m_state, bios, "@bios.lua", m_globals );
m_mainRoutine = new LuaThread( m_state, value, m_globals );
return MachineResult.OK;
}
catch( CompileException e )
{
close();
return MachineResult.error( e );
unload();
}
catch( Exception e )
catch( IOException e )
{
ComputerCraft.log.warn( "Could not load bios.lua", e );
close();
return MachineResult.GENERIC_ERROR;
ComputerCraft.log.warn( "Could not load bios.lua ", e );
unload();
}
}
@Override
public MachineResult handleEvent( String eventName, Object[] arguments )
public void handleEvent( String eventName, Object[] arguments )
{
if( m_mainRoutine == null ) return MachineResult.OK;
if( m_mainRoutine == null ) return;
if( m_eventFilter != null && eventName != null && !eventName.equals( m_eventFilter ) && !eventName.equals( "terminate" ) )
{
return MachineResult.OK;
return;
}
// If the soft abort has been cleared then we can reset our flag.
timeout.refresh();
if( !timeout.isSoftAborted() ) debug.thrownSoftAbort = false;
try
{
Varargs resumeArgs = Constants.NONE;
@@ -172,47 +222,63 @@ public class CobaltLuaMachine implements ILuaMachine
resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) );
}
// Resume the current thread, or the main one when first starting off.
LuaThread thread = m_state.getCurrentThread();
if( thread == null || thread == m_state.getMainThread() ) thread = m_mainRoutine;
Varargs results = LuaThread.run( thread, resumeArgs );
if( timeout.isHardAborted() ) throw HardAbortError.INSTANCE;
if( results == null ) return MachineResult.PAUSE;
Varargs results = LuaThread.run( m_mainRoutine, resumeArgs );
if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage );
LuaValue filter = results.first();
m_eventFilter = filter.isString() ? filter.toString() : null;
if( m_mainRoutine.getStatus().equals( "dead" ) )
{
close();
return MachineResult.GENERIC_ERROR;
}
else
{
return MachineResult.OK;
}
if( m_mainRoutine.getStatus().equals( "dead" ) ) unload();
}
catch( HardAbortError | InterruptedException e )
catch( LuaError | HardAbortError e )
{
close();
return MachineResult.TIMEOUT;
}
catch( LuaError e )
{
close();
unload();
ComputerCraft.log.warn( "Top level coroutine errored", e );
return MachineResult.error( e );
}
finally
{
m_softAbortMessage = null;
m_hardAbortMessage = null;
}
}
@Override
public void close()
public void softAbort( String abortMessage )
{
LuaState state = m_state;
if( state == null ) return;
m_softAbortMessage = abortMessage;
}
state.abandon();
@Override
public void hardAbort( String abortMessage )
{
m_softAbortMessage = abortMessage;
m_hardAbortMessage = abortMessage;
}
@Override
public boolean saveState( OutputStream output )
{
return false;
}
@Override
public boolean restoreState( InputStream input )
{
return false;
}
@Override
public boolean isFinished()
{
return m_mainRoutine == null;
}
@Override
public void unload()
{
if( m_state == null ) return;
m_state.abandon();
m_mainRoutine = null;
m_state = null;
m_globals = null;
@@ -232,17 +298,151 @@ public class CobaltLuaMachine implements ILuaMachine
table.rawset( methodName, new VarArgFunction()
{
@Override
public Varargs invoke( final LuaState state, Varargs args ) throws LuaError
public Varargs invoke( final LuaState state, Varargs _args ) throws LuaError
{
Object[] arguments = toObjects( args, 1 );
Object[] arguments = toObjects( _args, 1 );
Object[] results;
try
{
results = apiObject.callMethod( context, method, arguments );
results = apiObject.callMethod( new ILuaContext()
{
@Nonnull
@Override
public Object[] pullEvent( String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[0].equals( "terminate" ) )
{
throw new LuaException( "Terminated", 0 );
}
return results;
}
@Nonnull
@Override
public Object[] pullEventRaw( String filter ) throws InterruptedException
{
return yield( new Object[] { filter } );
}
@Nonnull
@Override
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
{
try
{
Varargs results = LuaThread.yieldBlocking( state, toValues( yieldArgs ) );
return toObjects( results, 1 );
}
catch( LuaError e )
{
throw new IllegalStateException( e );
}
}
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final ITask iTask = new ITask()
{
@Override
public Computer getOwner()
{
return m_computer;
}
@Override
public void execute()
{
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[results.length + 2];
eventArguments[0] = taskID;
eventArguments[1] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
m_computer.queueEvent( "task_complete", eventArguments );
}
else
{
m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
}
catch( LuaException e )
{
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, e.getMessage()
} );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error running task", t );
}
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t.toString()
} );
}
}
};
if( MainThread.queueTask( iTask ) )
{
return taskID;
}
else
{
throw new LuaException( "Task limit exceeded" );
}
}
@Override
public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException
{
// Issue task
final long taskID = issueMainThreadTask( task );
// Wait for response
while( true )
{
Object[] response = pullEvent( "task_complete" );
if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean )
{
if( ((Number) response[1]).intValue() == taskID )
{
Object[] returnValues = new Object[response.length - 3];
if( (Boolean) response[2] )
{
// Extract the return values from the event and return them
System.arraycopy( response, 3, returnValues, 0, returnValues.length );
return returnValues;
}
else
{
// Extract the error message from the event and raise it
if( response.length >= 4 && response[3] instanceof String )
{
throw new LuaException( (String) response[3] );
}
else
{
throw new LuaException();
}
}
}
}
}
}
}, method, arguments );
}
catch( InterruptedException e )
{
throw new InterruptedError( e );
throw new OrphanedThread();
}
catch( LuaException e )
{
@@ -254,7 +454,7 @@ public class CobaltLuaMachine implements ILuaMachine
{
ComputerCraft.log.error( "Error calling " + methodName + " on " + apiObject, t );
}
throw new LuaError( "Java Exception Thrown: " + t, 0 );
throw new LuaError( "Java Exception Thrown: " + t.toString(), 0 );
}
return toValues( results );
}
@@ -348,14 +548,22 @@ public class CobaltLuaMachine implements ILuaMachine
{
case Constants.TNIL:
case Constants.TNONE:
{
return null;
}
case Constants.TINT:
case Constants.TNUMBER:
{
return value.toDouble();
}
case Constants.TBOOLEAN:
{
return value.toBoolean();
}
case Constants.TSTRING:
{
return value.toString();
}
case Constants.TTABLE:
{
// Table:
@@ -403,7 +611,9 @@ public class CobaltLuaMachine implements ILuaMachine
return table;
}
default:
{
return null;
}
}
}
@@ -420,195 +630,101 @@ public class CobaltLuaMachine implements ILuaMachine
return objects;
}
/**
* A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly.
*/
private class TimeoutDebugHandler extends DebugHandler
private static class PrefixLoader extends VarArgFunction
{
private final TimeoutState timeout;
private int count = 0;
boolean thrownSoftAbort;
private boolean isPaused;
private int oldFlags;
private boolean oldInHook;
TimeoutDebugHandler()
{
timeout = CobaltLuaMachine.this.timeout;
}
private static final LuaString FUNCTION_STR = valueOf( "function" );
private static final LuaString EQ_STR = valueOf( "=" );
@Override
public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable
public Varargs invoke( LuaState state, Varargs args ) throws LuaError
{
di.pc = pc;
if( isPaused ) resetPaused( ds, di );
// We check our current pause/abort state every 128 instructions.
if( (count = (count + 1) & 127) == 0 )
switch( opcode )
{
// If we've been hard aborted or closed then abort.
if( timeout.isHardAborted() || m_state == null ) throw HardAbortError.INSTANCE;
timeout.refresh();
if( timeout.isPaused() )
case 0: // "load", // ( func [,chunkname] ) -> chunk | nil, msg
{
// Preserve the current state
isPaused = true;
oldInHook = ds.inhook;
oldFlags = di.flags;
// Suspend the state. This will probably throw, but we need to handle the case where it won't.
di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED;
LuaThread.suspend( ds.getLuaState() );
resetPaused( ds, di );
LuaValue func = args.arg( 1 ).checkFunction();
LuaString chunkname = args.arg( 2 ).optLuaString( FUNCTION_STR );
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
{
chunkname = OperationHelper.concat( EQ_STR, chunkname );
}
return BaseLib.loadStream( state, new StringInputStream( state, func ), chunkname );
}
case 1: // "loadstring", // ( string [,chunkname] ) -> chunk | nil, msg
{
LuaString script = args.arg( 1 ).checkLuaString();
LuaString chunkname = args.arg( 2 ).optLuaString( script );
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
{
chunkname = OperationHelper.concat( EQ_STR, chunkname );
}
return BaseLib.loadStream( state, script.toInputStream(), chunkname );
}
handleSoftAbort();
}
super.onInstruction( ds, di, pc );
}
@Override
public void poll() throws LuaError
{
// If we've been hard aborted or closed then abort.
LuaState state = m_state;
if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE;
timeout.refresh();
if( timeout.isPaused() ) LuaThread.suspendBlocking( state );
handleSoftAbort();
}
private void resetPaused( DebugState ds, DebugFrame di )
{
// Restore the previous paused state
isPaused = false;
ds.inhook = oldInHook;
di.flags = oldFlags;
}
private void handleSoftAbort() throws LuaError
{
// If we already thrown our soft abort error then don't do it again.
if( !timeout.isSoftAborted() || thrownSoftAbort ) return;
thrownSoftAbort = true;
throw new LuaError( TimeoutState.ABORT_MESSAGE );
return NONE;
}
}
private class CobaltLuaContext implements ILuaContext
private static class StringInputStream extends InputStream
{
@Nonnull
@Override
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
private final LuaState state;
private final LuaValue func;
private byte[] bytes;
private int offset, remaining = 0;
StringInputStream( LuaState state, LuaValue func )
{
try
{
LuaState state = m_state;
if( state == null ) throw new InterruptedException();
Varargs results = LuaThread.yieldBlocking( state, toValues( yieldArgs ) );
return toObjects( results, 1 );
}
catch( LuaError e )
{
throw new IllegalStateException( e.getMessage() );
}
this.state = state;
this.func = func;
}
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
public int read() throws IOException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final Runnable iTask = () -> {
if( remaining <= 0 )
{
LuaValue s;
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[results.length + 2];
eventArguments[0] = taskID;
eventArguments[1] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
m_computer.queueEvent( "task_complete", eventArguments );
}
else
{
m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
s = OperationHelper.noYield( state, () -> OperationHelper.call( state, func ) );
}
catch( LuaException e )
catch( LuaError e )
{
m_computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } );
throw new IOException( e );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error running task", t );
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t
} );
}
};
if( m_computer.queueMainThread( iTask ) )
{
return taskID;
}
else
{
throw new LuaException( "Task limit exceeded" );
}
}
@Override
public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException
{
// Issue task
final long taskID = issueMainThreadTask( task );
// Wait for response
while( true )
{
Object[] response = pullEvent( "task_complete" );
if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean )
if( s.isNil() )
{
if( ((Number) response[1]).intValue() == taskID )
{
Object[] returnValues = new Object[response.length - 3];
if( (Boolean) response[2] )
{
// Extract the return values from the event and return them
System.arraycopy( response, 3, returnValues, 0, returnValues.length );
return returnValues;
}
else
{
// Extract the error message from the event and raise it
if( response.length >= 4 && response[3] instanceof String )
{
throw new LuaException( (String) response[3] );
}
else
{
throw new LuaException();
}
}
}
return -1;
}
LuaString ls;
try
{
ls = s.strvalue();
}
catch( LuaError e )
{
throw new IOException( e );
}
bytes = ls.bytes;
offset = ls.offset;
remaining = ls.length;
if( remaining <= 0 )
{
return -1;
}
}
--remaining;
return bytes[offset++];
}
}
private static final class HardAbortError extends Error
private static class HardAbortError extends Error
{
private static final long serialVersionUID = 7954092008586367501L;
static final HardAbortError INSTANCE = new HardAbortError();
public static final HardAbortError INSTANCE = new HardAbortError();
private HardAbortError()
{

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