1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-15 12:40:30 +00:00

Merge branch 'mc-1.16.x' into mc-1.18.x

This commit is contained in:
Jonathan Coates 2022-05-22 15:03:01 +01:00
commit 5052718428
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
80 changed files with 1413 additions and 461 deletions

View File

@ -143,7 +143,7 @@ dependencies {
compileOnly fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116:api") compileOnly fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116:api")
runtimeOnly fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116") runtimeOnly fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116")
shade 'org.squiddev:Cobalt:0.5.2-SNAPSHOT' shade 'org.squiddev:Cobalt:0.5.5'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0'
@ -219,9 +219,32 @@ processResources {
try { try {
hash = ["git", "-C", projectDir, "rev-parse", "HEAD"].execute().text.trim() hash = ["git", "-C", projectDir, "rev-parse", "HEAD"].execute().text.trim()
def blacklist = ['GitHub', 'dan200', 'Daniel Ratcliffe'] def blacklist = ['GitHub', 'Daniel Ratcliffe', 'Weblate']
["git", "-C", projectDir, "log", "--format=tformat:%an%n%cn"].execute().text.split('\n').each {
if (!blacklist.contains(it)) contributors.add(it) // Extract all authors, commiters and co-authors from the git log.
def authors = ["git", "-C", projectDir, "log", "--format=tformat:%an <%ae>%n%cn <%ce>%n%(trailers:key=Co-authored-by,valueonly)"]
.execute().text.readLines().unique()
// We now pass this through git's mailmap to de-duplicate some authors.
def remapAuthors = ["git", "check-mailmap", "--stdin"].execute()
remapAuthors.withWriter { stdin ->
if (stdin !instanceof BufferedWriter) stdin = new BufferedWriter(stdin)
authors.forEach {
if (it == "") return
if (!it.endsWith(">")) it += ">" // Some commits have broken Co-Authored-By lines!
stdin.writeLine(it)
}
stdin.close()
}
// And finally extract out the actual name.
def emailRegex = ~/^([^<]+) <.+>$/
remapAuthors.text.readLines().forEach {
def matcher = it =~ emailRegex
matcher.find()
def name = matcher.group(1)
if (!blacklist.contains(name)) contributors.add(name)
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace() e.printStackTrace()

View File

@ -15,13 +15,11 @@ advanced turtles and pocket computers).
Several mouse events (@{mouse_click}, @{mouse_up}, @{mouse_scroll}) contain a "mouse button" code. This takes a Several mouse events (@{mouse_click}, @{mouse_up}, @{mouse_scroll}) contain a "mouse button" code. This takes a
numerical value depending on which button on your mouse was last pressed when this event occurred. numerical value depending on which button on your mouse was last pressed when this event occurred.
<table class="pretty-table"> | Button Code | Mouse Button |
<!-- Our markdown parser doesn't work on tables!? Guess I'll have to roll my own soonish :/. --> |------------:|---------------|
<tr><th>Button code</th><th>Mouse button</th></tr> | 1 | Left button |
<tr><td align="right">1</td><td>Left button</td></tr> | 2 | Right button |
<tr><td align="right">2</td><td>Right button</td></tr> | 3 | Middle button |
<tr><td align="right">3</td><td>Middle button</td></tr>
</table>
## Example ## Example
Print the button and the coordinates whenever the mouse is clicked. Print the button and the coordinates whenever the mouse is clicked.

99
doc/guides/local_ips.md Normal file
View File

@ -0,0 +1,99 @@
---
module: [kind=guide] local_ips
---
# Allowing access to local IPs
By default, ComputerCraft blocks access to local IP addresses for security. This means you can't normally access any
HTTP server running on your computer. However, this may be useful for testing programs without having a remote
server. You can unblock these IPs in the ComputerCraft config.
- [Minecraft 1.13 and later, CC:T 1.87.0 and later](#cc-1.87.0)
- [Minecraft 1.13 and later, CC:T 1.86.2 and earlier](#cc-1.86.2)
- [Minecraft 1.12.2 and earlier](#mc-1.12)
## Minecraft 1.13 and later, CC:T 1.87.0 and later {#cc-1.87.0}
The configuration file can be located at `serverconfig/computercraft-server.toml` inside the world folder on either
single-player or multiplayer. Look for lines that look like this:
```toml
#A list of rules which control behaviour of the "http" API for specific domains or IPs.
#Each rule is an item with a 'host' to match against, and a series of properties. The host may be a domain name ("pastebin.com"),
#wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). If no rules, the domain is blocked.
[[http.rules]]
host = "$private"
action = "deny"
```
On 1.95.0 and later, this will be a single entry with `host = "$private"`. On earlier versions, this will be a number of
`[[http.rules]]` with various IP addresses. You will want to remove all of the `[[http.rules]]` entires that have
`action = "deny"`. Then save the file and relaunch Minecraft (Server).
Here's what it should look like after removing:
```toml
#A list of rules which control behaviour of the "http" API for specific domains or IPs.
#Each rule is an item with a 'host' to match against, and a series of properties. The host may be a domain name ("pastebin.com"),
#wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). If no rules, the domain is blocked.
[[http.rules]]
#The maximum size (in bytes) that a computer can send or receive in one websocket packet.
max_websocket_message = 131072
host = "*"
#The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text.
max_upload = 4194304
action = "allow"
#The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client.
max_download = 16777216
#The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited.
timeout = 30000
```
## Minecraft 1.13 and later, CC:T 1.86.2 and earlier {#cc-1.86.2}
The configuration file for singleplayer is at `.minecraft/config/computercraft-common.toml`. Look for lines that look
like this:
```toml
#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
blacklist = ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"]
```
Remove everything inside the array, leaving the last line as `blacklist = []`. Then save the file and relaunch Minecraft.
Here's what it should look like after removing:
```toml
#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
blacklist = []
```
## Minecraft 1.12.2 and earlier {#mc-1.12}
On singleplayer, the configuration file is located at `.minecraft\config\ComputerCraft.cfg`. On multiplayer, the
configuration file is located at `<server folder>\config\ComputerCraft.cfg`. Look for lines that look like this:
```ini
# A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
# If this is empty then all explicitly allowed domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
# You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
S:blocked_domains <
127.0.0.0/8
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
fd00::/8
>
```
Delete everything between the `<>`, leaving the last line as `S:blocked_domains = <>`. Then save the file and relaunch
Minecraft (Server).
Here's what it should look like after removing:
```ini
# A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
# If this is empty then all explicitly allowed domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
# You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
S:blocked_domains <>
```

View File

@ -125,7 +125,7 @@ different.
First, we require the dfpwm module and call @{cc.audio.dfpwm.make_decoder} to construct a new decoder. This decoder First, we require the dfpwm module and call @{cc.audio.dfpwm.make_decoder} to construct a new decoder. This decoder
accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker. accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker.
As mentioned to above, @{speaker.playAudio} accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each As mentioned above, @{speaker.playAudio} accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
@{io.lines}, which provides a nice way to loop over chunks of a file. You can of course just use @{fs.open} and @{io.lines}, which provides a nice way to loop over chunks of a file. You can of course just use @{fs.open} and
@{fs.BinaryReadHandle.read} if you prefer. @{fs.BinaryReadHandle.read} if you prefer.
@ -136,22 +136,22 @@ You can mix together samples from different streams by adding their amplitudes,
samples, etc... samples, etc...
Let's put together a small demonstration here. We're going to add a small delay effect to the song above, so that you Let's put together a small demonstration here. We're going to add a small delay effect to the song above, so that you
hear a faint echo about a second later. hear a faint echo a second and a half later.
In order to do this, we'll follow a format similar to the previous example, decoding the audio and then playing it. In order to do this, we'll follow a format similar to the previous example, decoding the audio and then playing it.
However, we'll also add some new logic between those two steps, which loops over every sample in our chunk of audio, and However, we'll also add some new logic between those two steps, which loops over every sample in our chunk of audio, and
adds the sample from one second ago to it. adds the sample from 1.5 seconds ago to it.
For this, we'll need to keep track of the last 48k samples - exactly one seconds worth of audio. We can do this using a For this, we'll need to keep track of the last 72k samples - exactly 1.5 seconds worth of audio. We can do this using a
[Ring Buffer], which helps makes things a little more efficient. [Ring Buffer], which helps makes things a little more efficient.
```lua {data-peripheral=speaker} ```lua {data-peripheral=speaker}
local dfpwm = require("cc.audio.dfpwm") local dfpwm = require("cc.audio.dfpwm")
local speaker = peripheral.find("speaker") local speaker = peripheral.find("speaker")
-- Speakers play at 48kHz, so one second is 48k samples. We first fill our buffer -- Speakers play at 48kHz, so 1.5 seconds is 72k samples. We first fill our buffer
-- with 0s, as there's nothing to echo at the start of the track! -- with 0s, as there's nothing to echo at the start of the track!
local samples_i, samples_n = 1, 48000 local samples_i, samples_n = 1, 48000 * 1.5
local samples = {} local samples = {}
for i = 1, samples_n do samples[i] = 0 end for i = 1, samples_n do samples[i] = 0 end
@ -162,7 +162,7 @@ for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
for i = 1, #buffer do for i = 1, #buffer do
local original_value = buffer[i] local original_value = buffer[i]
-- Replace this sample with its current amplitude plus the amplitude from one second ago. -- Replace this sample with its current amplitude plus the amplitude from 1.5 seconds ago.
-- We scale both to ensure the resulting value is still between -128 and 127. -- We scale both to ensure the resulting value is still between -128 and 127.
buffer[i] = original_value * 0.6 + samples[samples_i] * 0.4 buffer[i] = original_value * 0.6 + samples[samples_i] * 0.4
@ -175,6 +175,11 @@ for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
while not speaker.playAudio(buffer) do while not speaker.playAudio(buffer) do
os.pullEvent("speaker_audio_empty") os.pullEvent("speaker_audio_empty")
end end
-- The audio processing above can be quite slow and preparing the first batch of audio
-- may timeout the computer. We sleep to avoid this.
-- There's definitely better ways of handling this - this is just an example!
sleep(0.05)
end end
``` ```

View File

@ -0,0 +1,95 @@
---
module: [kind=reference] feature_compat
---
# Lua 5.2/5.3 features in CC: Tweaked
CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However, Cobalt and CC:T implement additional features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 features) that are not available in base 5.1. This page lists all of the compatibility for these newer versions.
## Lua 5.2
| Feature | Supported? | Notes |
|---------------------------------------------------------------|------------|-------------------------------------------------------------------|
| `goto`/labels | ❌ | |
| `_ENV` | 🔶 | The `_ENV` global points to `getfenv()`, but it cannot be set. |
| `\z` escape | ✔ | |
| `\xNN` escape | ✔ | |
| Hex literal fractional/exponent parts | ✔ | |
| Empty statements | ❌ | |
| `__len` metamethod | ✔ | |
| `__ipairs` metamethod | ❌ | |
| `__pairs` metamethod | ✔ | |
| `bit32` library | ✔ | |
| `collectgarbage` isrunning, generational, incremental options | ❌ | `collectgarbage` does not exist in CC:T. |
| New `load` syntax | ✔ | |
| `loadfile` mode parameter | ✔ | Supports both 5.1 and 5.2+ syntax. |
| Removed `loadstring` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Removed `getfenv`, `setfenv` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| `rawlen` function | ✔ | |
| Negative index to `select` | ✔ | |
| Removed `unpack` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Arguments to `xpcall` | ❌ | |
| Second return value from `coroutine.running` | ❌ | |
| Removed `module` | ✔ | |
| `package.loaders` -> `package.searchers` | ❌ | |
| Second argument to loader functions | ✔ | |
| `package.config` | ✔ | |
| `package.searchpath` | ✔ | |
| Removed `package.seeall` | ✔ | |
| `string.dump` on functions with upvalues (blanks them out) | ✔ | |
| `string.rep` separator | ❌ | |
| `%g` match group | ❌ | |
| Removal of `%z` match group | ❌ | |
| Removed `table.maxn` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| `table.pack`/`table.unpack` | ✔ | |
| `math.log` base argument | ✔ | |
| Removed `math.log10` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| `*L` mode to `file:read` | ✔ | |
| `os.execute` exit type + return value | ❌ | `os.execute` does not exist in CC:T. |
| `os.exit` close argument | ❌ | `os.exit` does not exist in CC:T. |
| `istailcall` field in `debug.getinfo` | ❌ | |
| `nparams` field in `debug.getinfo` | ✔ | |
| `isvararg` field in `debug.getinfo` | ✔ | |
| `debug.getlocal` negative indices for varargs | ❌ | |
| `debug.getuservalue`/`debug.setuservalue` | ❌ | Userdata are rarely used in CC:T, so this is not necessary. |
| `debug.upvalueid` | ✔ | |
| `debug.upvaluejoin` | ✔ | |
| Tail call hooks | ❌ | |
| `=` prefix for chunks | ✔ | |
| Yield across C boundary | ✔ | |
| Removal of ambiguity error | ❌ | |
| Identifiers may no longer use locale-dependent letters | ✔ | |
| Ephemeron tables | ❌ | |
| Identical functions may be reused | ❌ | |
| Generational garbage collector | ❌ | Cobalt uses the built-in Java garbage collector. |
## Lua 5.3
| Feature | Supported? | Notes |
|---------------------------------------------------------------------------------------|------------|---------------------------|
| Integer subtype | ❌ | |
| Bitwise operators/floor division | ❌ | |
| `\u{XXX}` escape sequence | ✔ | |
| `utf8` library | ✔ | |
| removed `__ipairs` metamethod | ✔ | |
| `coroutine.isyieldable` | ❌ | |
| `string.dump` strip argument | ✔ | |
| `string.pack`/`string.unpack`/`string.packsize` | ✔ | |
| `table.move` | ❌ | |
| `math.atan2` -> `math.atan` | ❌ | |
| Removed `math.frexp`, `math.ldexp`, `math.pow`, `math.cosh`, `math.sinh`, `math.tanh` | ❌ | |
| `math.maxinteger`/`math.mininteger` | ❌ | |
| `math.tointeger` | ❌ | |
| `math.type` | ❌ | |
| `math.ult` | ❌ | |
| Removed `bit32` library | ❌ | |
| Remove `*` from `file:read` modes | ✔ | |
| Metamethods respected in `table.*`, `ipairs` | 🔶 | Only `__lt` is respected. |
## Lua 5.0
| Feature | Supported? | Notes |
|----------------------------------|------------|--------------------------------------------------|
| `arg` table | 🔶 | Only set in the shell - not used in functions. |
| `string.gfind` | ✔ | Equal to `string.gmatch`. |
| `table.getn` | ✔ | Equal to `#tbl`. |
| `table.setn` | ❌ | |
| `math.mod` | ✔ | Equal to `math.fmod`. |
| `table.foreach`/`table.foreachi` | ✔ | |
| `gcinfo` | ❌ | Cobalt uses the built-in Java garbage collector. |

View File

@ -1,6 +1,4 @@
--- The FS API allows you to manipulate files and the filesystem. --- @module fs
--
-- @module fs
--- Returns true if a path is mounted to the parent filesystem. --- Returns true if a path is mounted to the parent filesystem.
-- --

View File

@ -62,7 +62,7 @@ function print(...) end
-- @usage printError("Something went wrong!") -- @usage printError("Something went wrong!")
function printError(...) end function printError(...) end
--[[- Reads user input from the terminal, automatically handling arrow keys, --[[- Reads user input from the terminal. This automatically handles arrow keys,
pasting, character replacement, history scrollback, auto-completion, and pasting, character replacement, history scrollback, auto-completion, and
default values. default values.
@ -110,10 +110,15 @@ the prompt.
]] ]]
function read(replaceChar, history, completeFn, default) end function read(replaceChar, history, completeFn, default) end
--- The ComputerCraft and Minecraft version of the current computer environment. --- Stores the current ComputerCraft and Minecraft versions.
--
-- Outside of Minecraft (for instance, in an emulator) @{_HOST} will contain the
-- emulator's version instead.
-- --
-- For example, `ComputerCraft 1.93.0 (Minecraft 1.15.2)`. -- For example, `ComputerCraft 1.93.0 (Minecraft 1.15.2)`.
-- @usage _HOST -- @usage Print the current computer's environment.
--
-- print(_HOST)
-- @since 1.76 -- @since 1.76
_HOST = _HOST _HOST = _HOST

View File

@ -1,14 +1,13 @@
--- The http library allows communicating with web servers, sending and --- Make HTTP requests, sending and receiving data to a remote web server.
-- receiving data from them.
-- --
-- @module http -- @module http
-- @since 1.1 -- @since 1.1
-- @see local_ips To allow accessing servers running on your local network.
--- Asynchronously make a HTTP request to the given url. --- Asynchronously make a HTTP request to the given url.
-- --
-- This returns immediately, a [`http_success`](#http-success-event) or -- This returns immediately, a @{http_success} or @{http_failure} will be queued
-- [`http_failure`](#http-failure-event) will be queued once the request has -- once the request has completed.
-- completed.
-- --
-- @tparam string url The url to request -- @tparam string url The url to request
-- @tparam[opt] string body An optional string containing the body of the -- @tparam[opt] string body An optional string containing the body of the
@ -112,9 +111,8 @@ function post(...) end
--- Asynchronously determine whether a URL can be requested. --- Asynchronously determine whether a URL can be requested.
-- --
-- If this returns `true`, one should also listen for [`http_check` -- If this returns `true`, one should also listen for @{http_check} which will
-- events](#http-check-event) which will container further information about -- container further information about whether the URL is allowed or not.
-- whether the URL is allowed or not.
-- --
-- @tparam string url The URL to check. -- @tparam string url The URL to check.
-- @treturn true When this url is not invalid. This does not imply that it is -- @treturn true When this url is not invalid. This does not imply that it is
@ -128,9 +126,8 @@ function checkURLAsync(url) end
--- Determine whether a URL can be requested. --- Determine whether a URL can be requested.
-- --
-- If this returns `true`, one should also listen for [`http_check` -- If this returns `true`, one should also listen for @{http_check} which will
-- events](#http-check-event) which will container further information about -- container further information about whether the URL is allowed or not.
-- whether the URL is allowed or not.
-- --
-- @tparam string url The URL to check. -- @tparam string url The URL to check.
-- @treturn true When this url is valid and can be requested via @{http.request}. -- @treturn true When this url is valid and can be requested via @{http.request}.
@ -168,9 +165,8 @@ function websocket(url, headers) end
--- Asynchronously open a websocket. --- Asynchronously open a websocket.
-- --
-- This returns immediately, a [`websocket_success`](#websocket-success-event) -- This returns immediately, a @{websocket_success} or @{websocket_failure}
-- or [`websocket_failure`](#websocket-failure-event) will be queued once the -- will be queued once the request has completed.
-- request has completed.
-- --
-- @tparam string url The websocket url to connect to. This should have the -- @tparam string url The websocket url to connect to. This should have the
-- `ws://` or `wss://` protocol. -- `ws://` or `wss://` protocol.

View File

@ -1,9 +1,7 @@
; -*- mode: Lisp;-*- ; -*- mode: Lisp;-*-
(sources (sources
/doc/events/ /doc/
/doc/guides/
/doc/stub/
/build/docs/luaJavadoc/ /build/docs/luaJavadoc/
/src/main/resources/*/computercraft/lua/bios.lua /src/main/resources/*/computercraft/lua/bios.lua
/src/main/resources/*/computercraft/lua/rom/ /src/main/resources/*/computercraft/lua/rom/
@ -29,7 +27,8 @@
(peripheral Peripherals) (peripheral Peripherals)
(generic_peripheral "Generic Peripherals") (generic_peripheral "Generic Peripherals")
(event Events) (event Events)
(guide Guides)) (guide Guides)
(reference Reference))
(library-path (library-path
/doc/stub/ /doc/stub/

View File

@ -188,8 +188,8 @@ public interface IArguments
* *
* Classes implementing this interface may choose to implement a more optimised version which does not copy the * Classes implementing this interface may choose to implement a more optimised version which does not copy the
* table, instead returning a wrapper version, making it more efficient. However, the caller must guarantee that * table, instead returning a wrapper version, making it more efficient. However, the caller must guarantee that
* they do not access off the computer thread (and so should not be used with main-thread functions) or once the * they do not access the table the computer thread (and so should not be used with main-thread functions) or once
* function call has finished (for instance, in callbacks). * the initial call has finished (for instance, in a callback to {@link MethodResult#pullEvent}).
* *
* @param index The argument number. * @param index The argument number.
* @return The argument's value. * @return The argument's value.
@ -448,7 +448,10 @@ public interface IArguments
* This is called when the current function finishes, before any main thread tasks have run. * This is called when the current function finishes, before any main thread tasks have run.
* *
* Called when the current function returns, and so some values are no longer guaranteed to be safe to access. * Called when the current function returns, and so some values are no longer guaranteed to be safe to access.
*
* @deprecated This method was an internal implementation detail and is no longer used.
*/ */
@Deprecated
default void releaseImmediate() default void releaseImmediate()
{ {
} }

View File

@ -5,12 +5,10 @@
*/ */
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
/** /**
* An implementation of {@link IArguments} which wraps an array of {@link Object}. * An implementation of {@link IArguments} which wraps an array of {@link Object}.
@ -19,7 +17,6 @@ public final class ObjectArguments implements IArguments
{ {
private static final IArguments EMPTY = new ObjectArguments(); private static final IArguments EMPTY = new ObjectArguments();
private boolean released = false;
private final List<Object> args; private final List<Object> args;
@Deprecated @Deprecated
@ -67,34 +64,4 @@ public final class ObjectArguments implements IArguments
{ {
return args.toArray(); return args.toArray();
} }
@Nonnull
@Override
public LuaTable<?, ?> getTableUnsafe( int index ) throws LuaException
{
if( released )
{
throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been released" );
}
return IArguments.super.getTableUnsafe( index );
}
@Nonnull
@Override
public Optional<LuaTable<?, ?>> optTableUnsafe( int index ) throws LuaException
{
if( released )
{
throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been released" );
}
return IArguments.super.optTableUnsafe( index );
}
@Override
public void releaseImmediate()
{
released = true;
}
} }

View File

@ -123,8 +123,6 @@ public final class ClientRegistry
@SubscribeEvent @SubscribeEvent
public static void setupClient( FMLClientSetupEvent event ) public static void setupClient( FMLClientSetupEvent event )
{ {
registerContainers();
// While turtles themselves are not transparent, their upgrades may be. // While turtles themselves are not transparent, their upgrades may be.
ItemBlockRenderTypes.setRenderLayer( Registry.ModBlocks.TURTLE_NORMAL.get(), RenderType.translucent() ); ItemBlockRenderTypes.setRenderLayer( Registry.ModBlocks.TURTLE_NORMAL.get(), RenderType.translucent() );
ItemBlockRenderTypes.setRenderLayer( Registry.ModBlocks.TURTLE_ADVANCED.get(), RenderType.translucent() ); ItemBlockRenderTypes.setRenderLayer( Registry.ModBlocks.TURTLE_ADVANCED.get(), RenderType.translucent() );
@ -139,14 +137,18 @@ public final class ClientRegistry
BlockEntityRenderers.register( Registry.ModBlockEntities.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new ); BlockEntityRenderers.register( Registry.ModBlockEntities.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new );
BlockEntityRenderers.register( Registry.ModBlockEntities.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new ); BlockEntityRenderers.register( Registry.ModBlockEntities.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new );
registerItemProperty( "state", event.enqueueWork( () -> {
( stack, world, player, random ) -> ItemPocketComputer.getState( stack ).ordinal(), registerContainers();
Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED
); registerItemProperty( "state",
registerItemProperty( "coloured", ( stack, world, player, random ) -> ItemPocketComputer.getState( stack ).ordinal(),
( stack, world, player, random ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0, Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED
Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED );
); registerItemProperty( "coloured",
( stack, world, player, random ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0,
Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED
);
} );
} }
@SafeVarargs @SafeVarargs

View File

@ -30,8 +30,8 @@ import java.util.OptionalLong;
import java.util.function.Function; import java.util.function.Function;
/** /**
* The FS API provides access to the computer's files and filesystem, allowing you to manipulate files, directories and * Interact with the computer's files and filesystem, allowing you to manipulate files, directories and paths. This
* paths. This includes: * includes:
* *
* <ul> * <ul>
* <li>**Reading and writing files:** Call {@link #open} to obtain a file "handle", which can be used to read from or * <li>**Reading and writing files:** Call {@link #open} to obtain a file "handle", which can be used to read from or

View File

@ -28,7 +28,7 @@ import java.util.Optional;
import static dan200.computercraft.core.apis.TableHelper.*; import static dan200.computercraft.core.apis.TableHelper.*;
/** /**
* The http library allows communicating with web servers, sending and receiving data from them. * Placeholder description, please ignore.
* *
* @cc.module http * @cc.module http
* @hidden * @hidden

View File

@ -11,7 +11,7 @@ import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
/** /**
* Interact with redstone attached to this computer. * Get and set redstone signals adjacent to this computer.
* *
* The {@link RedstoneAPI} library exposes three "types" of redstone control: * The {@link RedstoneAPI} library exposes three "types" of redstone control:
* - Binary input/output ({@link #setOutput}/{@link #getInput}): These simply check if a redstone wire has any input or * - Binary input/output ({@link #setOutput}/{@link #getInput}): These simply check if a redstone wire has any input or

View File

@ -16,7 +16,8 @@ import dan200.computercraft.shared.util.Colour;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
/** /**
* The Terminal API provides functions for writing text to the terminal and monitors, and drawing ASCII graphics. * Interact with a computer's terminal or monitors, writing text and drawing
* ASCII graphics.
* *
* @cc.module term * @cc.module term
*/ */

View File

@ -14,6 +14,7 @@ import dan200.computercraft.shared.util.StringUtil;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
/** /**
* A base class for all objects which interact with a terminal. Namely the {@link TermAPI} and monitors. * A base class for all objects which interact with a terminal. Namely the {@link TermAPI} and monitors.
@ -283,9 +284,9 @@ public abstract class TermMethods
* }</pre> * }</pre>
*/ */
@LuaFunction @LuaFunction
public final void blit( String text, String textColour, String backgroundColour ) throws LuaException public final void blit( ByteBuffer text, ByteBuffer textColour, ByteBuffer backgroundColour ) throws LuaException
{ {
if( textColour.length() != text.length() || backgroundColour.length() != text.length() ) if( textColour.remaining() != text.remaining() || backgroundColour.remaining() != text.remaining() )
{ {
throw new LuaException( "Arguments must be the same length" ); throw new LuaException( "Arguments must be the same length" );
} }
@ -294,7 +295,7 @@ public abstract class TermMethods
synchronized( terminal ) synchronized( terminal )
{ {
terminal.blit( text, textColour, backgroundColour ); terminal.blit( text, textColour, backgroundColour );
terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() ); terminal.setCursorPos( terminal.getCursorX() + text.remaining(), terminal.getCursorY() );
} }
} }

View File

@ -32,10 +32,10 @@ public class AddressRuleConfig
config.setComment( "timeout", "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." ); config.setComment( "timeout", "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." );
config.add( "timeout", AddressRule.TIMEOUT ); config.add( "timeout", AddressRule.TIMEOUT );
config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client." ); config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request.\nNote that responses may receive more data than allowed, but this data will not\nbe returned to the client." );
config.set( "max_download", AddressRule.MAX_DOWNLOAD ); config.set( "max_download", AddressRule.MAX_DOWNLOAD );
config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text." ); config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This\nincludes headers and POST text." );
config.set( "max_upload", AddressRule.MAX_UPLOAD ); config.set( "max_upload", AddressRule.MAX_UPLOAD );
config.setComment( "max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet." ); config.setComment( "max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet." );

View File

@ -53,6 +53,7 @@ import java.util.concurrent.locks.ReentrantLock;
*/ */
final class ComputerExecutor final class ComputerExecutor
{ {
static ILuaMachine.Factory luaFactory = CobaltLuaMachine::new;
private static final int QUEUE_LIMIT = 256; private static final int QUEUE_LIMIT = 256;
private final Computer computer; private final Computer computer;
@ -400,7 +401,7 @@ final class ComputerExecutor
} }
// Create the lua machine // Create the lua machine
ILuaMachine machine = new CobaltLuaMachine( computer, timeout ); ILuaMachine machine = luaFactory.create( computer, timeout );
// Add the APIs. We unwrap them (yes, this is horrible) to get access to the underlying object. // Add the APIs. We unwrap them (yes, this is horrible) to get access to the underlying object.
for( ILuaAPI api : apis ) machine.addAPI( api instanceof ApiWrapper ? ((ApiWrapper) api).getDelegate() : api ); for( ILuaAPI api : apis ) machine.addAPI( api instanceof ApiWrapper ? ((ApiWrapper) api).getDelegate() : api );

View File

@ -13,6 +13,7 @@ import javax.annotation.Nullable;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
@ -49,11 +50,11 @@ import static dan200.computercraft.core.computer.TimeoutState.TIMEOUT;
public final class ComputerThread public final class ComputerThread
{ {
/** /**
* How often the computer thread monitor should run, in milliseconds. * How often the computer thread monitor should run.
* *
* @see Monitor * @see Monitor
*/ */
private static final int MONITOR_WAKEUP = 100; private static final long MONITOR_WAKEUP = TimeUnit.MILLISECONDS.toNanos( 100 );
/** /**
* The target latency between executing two tasks on a single machine. * The target latency between executing two tasks on a single machine.
@ -76,6 +77,13 @@ public final class ComputerThread
*/ */
private static final long LATENCY_MAX_TASKS = DEFAULT_LATENCY / DEFAULT_MIN_PERIOD; private static final long LATENCY_MAX_TASKS = DEFAULT_LATENCY / DEFAULT_MIN_PERIOD;
/**
* Time difference between reporting crashed threads.
*
* @see TaskRunner#reportTimeout(ComputerExecutor, long)
*/
private static final long REPORT_DEBOUNCE = TimeUnit.SECONDS.toNanos( 1 );
/** /**
* Lock used for modifications to the array of current threads. * Lock used for modifications to the array of current threads.
*/ */
@ -102,6 +110,8 @@ public final class ComputerThread
private static final ReentrantLock computerLock = new ReentrantLock(); private static final ReentrantLock computerLock = new ReentrantLock();
private static final Condition hasWork = computerLock.newCondition(); private static final Condition hasWork = computerLock.newCondition();
private static final AtomicInteger idleWorkers = new AtomicInteger( 0 );
private static final Condition monitorWakeup = computerLock.newCondition();
/** /**
* Active queues to execute. * Active queues to execute.
@ -135,7 +145,7 @@ public final class ComputerThread
if( runners == null ) if( runners == null )
{ {
// TODO: Change the runners length on config reloads // TODO: Update this on config reloads. Or possibly on world restarts?
runners = new TaskRunner[ComputerCraft.computerThreads]; runners = new TaskRunner[ComputerCraft.computerThreads];
// latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for // latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for
@ -227,9 +237,14 @@ public final class ComputerThread
executor.virtualRuntime = Math.max( newRuntime, executor.virtualRuntime ); executor.virtualRuntime = Math.max( newRuntime, executor.virtualRuntime );
boolean wasBusy = isBusy();
// Add to the queue, and signal the workers. // Add to the queue, and signal the workers.
computerQueue.add( executor ); computerQueue.add( executor );
hasWork.signal(); hasWork.signal();
// If we've transitioned into a busy state, notify the monitor. This will cause it to sleep for scaledPeriod
// instead of the longer wakeup duration.
if( !wasBusy && isBusy() ) monitorWakeup.signal();
} }
finally finally
{ {
@ -346,6 +361,17 @@ public final class ComputerThread
return !computerQueue.isEmpty(); return !computerQueue.isEmpty();
} }
/**
* Check if we have more work queued than we have capacity for. Effectively a more fine-grained version of
* {@link #hasPendingWork()}.
*
* @return If the computer threads are busy.
*/
private static boolean isBusy()
{
return computerQueue.size() > idleWorkers.get();
}
/** /**
* Observes all currently active {@link TaskRunner}s and terminates their tasks once they have exceeded the hard * Observes all currently active {@link TaskRunner}s and terminates their tasks once they have exceeded the hard
* abort limit. * abort limit.
@ -357,76 +383,93 @@ public final class ComputerThread
@Override @Override
public void run() public void run()
{ {
try while( true )
{ {
while( true ) computerLock.lock();
try
{ {
Thread.sleep( MONITOR_WAKEUP ); // If we've got more work than we have capacity for it, then we'll need to pause a task soon, so
// sleep for a single pause duration. Otherwise we only need to wake up to set the soft/hard abort
// flags, which are far less granular.
monitorWakeup.awaitNanos( isBusy() ? scaledPeriod() : MONITOR_WAKEUP );
}
catch( InterruptedException e )
{
ComputerCraft.log.error( "Monitor thread interrupted. Computers may behave very badly!", e );
break;
}
finally
{
computerLock.unlock();
}
TaskRunner[] currentRunners = ComputerThread.runners; checkRunners();
if( currentRunners != null ) }
}
private static void checkRunners()
{
TaskRunner[] currentRunners = ComputerThread.runners;
if( currentRunners == null ) return;
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;
// Refresh the timeout state. Will set the pause/soft timeout flags as appropriate.
executor.timeout.refresh();
// 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 * 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.
runner.reportTimeout( executor, afterStart );
runner.running = false;
runner.owner.interrupt();
ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null );
if( thisExecutor != null ) afterWork( runner, executor );
synchronized( threadLock )
{ {
for( int i = 0; i < currentRunners.length; i++ ) if( running && runners.length > i && runners[i] == runner )
{ {
TaskRunner runner = currentRunners[i]; runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start();
// 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 * 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();
}
}
}
else 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 )
catch( InterruptedException ignored ) {
{ // If we've hard aborted but we're still not dead, dump the stack trace and interrupt
// the task.
runner.reportTimeout( executor, afterStart );
runner.owner.interrupt();
}
} }
} }
} }
@ -441,6 +484,7 @@ public final class ComputerThread
private static final class TaskRunner implements Runnable private static final class TaskRunner implements Runnable
{ {
Thread owner; Thread owner;
long lastReport = Long.MIN_VALUE;
volatile boolean running = true; volatile boolean running = true;
final AtomicReference<ComputerExecutor> currentExecutor = new AtomicReference<>(); final AtomicReference<ComputerExecutor> currentExecutor = new AtomicReference<>();
@ -460,6 +504,7 @@ public final class ComputerThread
computerLock.lockInterruptibly(); computerLock.lockInterruptibly();
try try
{ {
idleWorkers.incrementAndGet();
while( computerQueue.isEmpty() ) hasWork.await(); while( computerQueue.isEmpty() ) hasWork.await();
executor = computerQueue.pollFirst(); executor = computerQueue.pollFirst();
assert executor != null : "hasWork should ensure we never receive null work"; assert executor != null : "hasWork should ensure we never receive null work";
@ -467,6 +512,7 @@ public final class ComputerThread
finally finally
{ {
computerLock.unlock(); computerLock.unlock();
idleWorkers.decrementAndGet();
} }
} }
catch( InterruptedException ignored ) catch( InterruptedException ignored )
@ -516,27 +562,32 @@ public final class ComputerThread
} }
} }
} }
}
private static void timeoutTask( ComputerExecutor executor, Thread thread, long time ) private void reportTimeout( ComputerExecutor executor, long time )
{
if( !ComputerCraft.logComputerErrors ) return;
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() )
{ {
builder.append( "\n at " ).append( element ); if( !ComputerCraft.logComputerErrors ) return;
}
ComputerCraft.log.warn( builder.toString() ); // Attempt to debounce stack trace reporting, limiting ourselves to one every second.
long now = System.nanoTime();
if( lastReport != Long.MIN_VALUE && now - lastReport - REPORT_DEBOUNCE <= 0 ) return;
lastReport = now;
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( owner.getName() )
.append( " is currently " )
.append( owner.getState() );
Object blocking = LockSupport.getBlocker( owner );
if( blocking != null ) builder.append( "\n on " ).append( blocking );
for( StackTraceElement element : owner.getStackTrace() )
{
builder.append( "\n at " ).append( element );
}
ComputerCraft.log.warn( builder.toString() );
}
} }
} }

View File

@ -23,8 +23,8 @@ import java.util.concurrent.atomic.AtomicLong;
* {@link MainThread} starts cool, and runs as many tasks as it can in the current {@link #budget}ns. Any external tasks * {@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 * (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 * Next tick, we add {@link ComputerCraft#maxMainGlobalTime} to our budget (clamp it to that value too). If we're still
* still over budget, then we should not execute <em>any</em> work (either as part of {@link MainThread} or externally). * over budget, then we should not execute <em>any</em> work (either as part of {@link MainThread} or externally).
*/ */
public final class MainThread public final class MainThread
{ {

View File

@ -86,7 +86,7 @@ public final class TimeoutState
/** /**
* Recompute the {@link #isSoftAborted()} and {@link #isPaused()} flags. * Recompute the {@link #isSoftAborted()} and {@link #isPaused()} flags.
*/ */
public void refresh() public synchronized void refresh()
{ {
// Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we // Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we
// need to handle overflow. // need to handle overflow.
@ -153,7 +153,7 @@ public final class TimeoutState
* *
* @see #nanoCumulative() * @see #nanoCumulative()
*/ */
void pauseTimer() synchronized void pauseTimer()
{ {
// We set the cumulative time to difference between current time and "nominal start time". // We set the cumulative time to difference between current time and "nominal start time".
cumulativeElapsed = System.nanoTime() - cumulativeStart; cumulativeElapsed = System.nanoTime() - cumulativeStart;
@ -163,7 +163,7 @@ public final class TimeoutState
/** /**
* Resets the cumulative time and resets the abort flags. * Resets the cumulative time and resets the abort flags.
*/ */
void stopTimer() synchronized void stopTimer()
{ {
cumulativeElapsed = 0; cumulativeElapsed = 0;
paused = softAbort = hardAbort = false; paused = softAbort = hardAbort = false;

View File

@ -6,7 +6,6 @@
package dan200.computercraft.core.lua; package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.api.lua.MethodResult;
@ -41,7 +40,7 @@ class BasicFunction extends VarArgFunction
@Override @Override
public Varargs invoke( LuaState luaState, Varargs args ) throws LuaError public Varargs invoke( LuaState luaState, Varargs args ) throws LuaError
{ {
IArguments arguments = CobaltLuaMachine.toArguments( args ); VarargArguments arguments = VarargArguments.of( args );
MethodResult results; MethodResult results;
try try
{ {
@ -61,7 +60,7 @@ class BasicFunction extends VarArgFunction
} }
finally finally
{ {
arguments.releaseImmediate(); arguments.close();
} }
if( results.getCallback() != null ) if( results.getCallback() != null )

View File

@ -420,11 +420,6 @@ public class CobaltLuaMachine implements ILuaMachine
return objects; return objects;
} }
static IArguments toArguments( Varargs values )
{
return values == Constants.NONE ? VarargArguments.EMPTY : new VarargArguments( values );
}
/** /**
* A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly. * A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly.
*/ */
@ -453,24 +448,9 @@ public class CobaltLuaMachine implements ILuaMachine
// We check our current pause/abort state every 128 instructions. // We check our current pause/abort state every 128 instructions.
if( (count = (count + 1) & 127) == 0 ) if( (count = (count + 1) & 127) == 0 )
{ {
// If we've been hard aborted or closed then abort.
if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE; if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE;
if( timeout.isPaused() ) handlePause( ds, di );
timeout.refresh(); if( timeout.isSoftAborted() ) handleSoftAbort();
if( timeout.isPaused() )
{
// 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 );
}
handleSoftAbort();
} }
super.onInstruction( ds, di, pc ); super.onInstruction( ds, di, pc );
@ -479,13 +459,10 @@ public class CobaltLuaMachine implements ILuaMachine
@Override @Override
public void poll() throws LuaError public void poll() throws LuaError
{ {
// If we've been hard aborted or closed then abort.
LuaState state = CobaltLuaMachine.this.state; LuaState state = CobaltLuaMachine.this.state;
if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE; if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE;
timeout.refresh();
if( timeout.isPaused() ) LuaThread.suspendBlocking( state ); if( timeout.isPaused() ) LuaThread.suspendBlocking( state );
handleSoftAbort(); if( timeout.isSoftAborted() ) handleSoftAbort();
} }
private void resetPaused( DebugState ds, DebugFrame di ) private void resetPaused( DebugState ds, DebugFrame di )
@ -499,11 +476,24 @@ public class CobaltLuaMachine implements ILuaMachine
private void handleSoftAbort() throws LuaError private void handleSoftAbort() throws LuaError
{ {
// If we already thrown our soft abort error then don't do it again. // If we already thrown our soft abort error then don't do it again.
if( !timeout.isSoftAborted() || thrownSoftAbort ) return; if( thrownSoftAbort ) return;
thrownSoftAbort = true; thrownSoftAbort = true;
throw new LuaError( TimeoutState.ABORT_MESSAGE ); throw new LuaError( TimeoutState.ABORT_MESSAGE );
} }
private void handlePause( DebugState ds, DebugFrame di ) throws LuaError, UnwindThrowable
{
// 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 );
}
} }
private static final class HardAbortError extends Error private static final class HardAbortError extends Error

View File

@ -7,6 +7,8 @@ package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.IDynamicLuaObject; import dan200.computercraft.api.lua.IDynamicLuaObject;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.TimeoutState;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -63,4 +65,9 @@ public interface ILuaMachine
* Close the Lua machine, aborting any running functions and deleting the internal state. * Close the Lua machine, aborting any running functions and deleting the internal state.
*/ */
void close(); void close();
interface Factory
{
ILuaMachine create( Computer computer, TimeoutState timeout );
}
} }

View File

@ -51,7 +51,7 @@ class ResultInterpreterFunction extends ResumableVarArgFunction<ResultInterprete
@Override @Override
protected Varargs invoke( LuaState state, DebugFrame debugFrame, Varargs args ) throws LuaError, UnwindThrowable protected Varargs invoke( LuaState state, DebugFrame debugFrame, Varargs args ) throws LuaError, UnwindThrowable
{ {
IArguments arguments = CobaltLuaMachine.toArguments( args ); VarargArguments arguments = VarargArguments.of( args );
MethodResult results; MethodResult results;
try try
{ {
@ -71,7 +71,7 @@ class ResultInterpreterFunction extends ResumableVarArgFunction<ResultInterprete
} }
finally finally
{ {
arguments.releaseImmediate(); arguments.close();
} }
ILuaCallback callback = results.getCallback(); ILuaCallback callback = results.getCallback();

View File

@ -133,7 +133,7 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object>
private void checkValid() private void checkValid()
{ {
if( arguments.released ) if( arguments.closed )
{ {
throw new IllegalStateException( "Cannot use LuaTable after IArguments has been released" ); throw new IllegalStateException( "Cannot use LuaTable after IArguments has been released" );
} }

View File

@ -15,19 +15,24 @@ import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Optional; import java.util.Optional;
class VarargArguments implements IArguments final class VarargArguments implements IArguments
{ {
static final IArguments EMPTY = new VarargArguments( Constants.NONE ); private static final VarargArguments EMPTY = new VarargArguments( Constants.NONE );
boolean released; boolean closed;
private final Varargs varargs; private final Varargs varargs;
private Object[] cache; private Object[] cache;
VarargArguments( Varargs varargs ) private VarargArguments( Varargs varargs )
{ {
this.varargs = varargs; this.varargs = varargs;
} }
static VarargArguments of( Varargs values )
{
return values == Constants.NONE ? EMPTY : new VarargArguments( values );
}
@Override @Override
public int count() public int count()
{ {
@ -104,9 +109,9 @@ class VarargArguments implements IArguments
@Override @Override
public dan200.computercraft.api.lua.LuaTable<?, ?> getTableUnsafe( int index ) throws LuaException public dan200.computercraft.api.lua.LuaTable<?, ?> getTableUnsafe( int index ) throws LuaException
{ {
if( released ) if( closed )
{ {
throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been released" ); throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been closed." );
} }
LuaValue value = varargs.arg( index + 1 ); LuaValue value = varargs.arg( index + 1 );
@ -118,9 +123,9 @@ class VarargArguments implements IArguments
@Override @Override
public Optional<dan200.computercraft.api.lua.LuaTable<?, ?>> optTableUnsafe( int index ) throws LuaException public Optional<dan200.computercraft.api.lua.LuaTable<?, ?>> optTableUnsafe( int index ) throws LuaException
{ {
if( released ) if( closed )
{ {
throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been released" ); throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been closed." );
} }
LuaValue value = varargs.arg( index + 1 ); LuaValue value = varargs.arg( index + 1 );
@ -129,9 +134,8 @@ class VarargArguments implements IArguments
return Optional.of( new TableImpl( this, (LuaTable) value ) ); return Optional.of( new TableImpl( this, (LuaTable) value ) );
} }
@Override public void close()
public void releaseImmediate()
{ {
released = true; closed = true;
} }
} }

View File

@ -11,6 +11,7 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
public class Terminal public class Terminal
{ {
@ -191,7 +192,7 @@ public class Terminal
return palette; return palette;
} }
public synchronized void blit( String text, String textColour, String backgroundColour ) public synchronized void blit( ByteBuffer text, ByteBuffer textColour, ByteBuffer backgroundColour )
{ {
int x = cursorX; int x = cursorX;
int y = cursorY; int y = cursorY;

View File

@ -5,6 +5,8 @@
*/ */
package dan200.computercraft.core.terminal; package dan200.computercraft.core.terminal;
import java.nio.ByteBuffer;
public class TextBuffer public class TextBuffer
{ {
private final char[] text; private final char[] text;
@ -42,6 +44,19 @@ public class TextBuffer
} }
} }
public void write( ByteBuffer text, int start )
{
int pos = start;
start = Math.max( start, 0 );
int length = text.remaining();
int end = Math.min( start + length, pos + length );
end = Math.min( end, this.text.length );
for( int i = start; i < end; i++ )
{
this.text[i] = (char) (text.get( i - pos ) & 0xFF);
}
}
public void write( TextBuffer text ) public void write( TextBuffer text )
{ {
int end = Math.min( text.length(), this.text.length ); int end = Math.min( text.length(), this.text.length );

View File

@ -29,7 +29,6 @@ import net.minecraft.world.level.storage.loot.entries.LootTableReference;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import net.minecraftforge.event.*; import net.minecraftforge.event.*;
import net.minecraftforge.event.entity.player.PlayerContainerEvent; import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.event.server.ServerStartingEvent; import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.event.server.ServerStoppedEvent; import net.minecraftforge.event.server.ServerStoppedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
@ -92,22 +91,21 @@ public final class CommonHooks
{ {
ComputerMBean.register(); ComputerMBean.register();
} }
}
@SubscribeEvent resetState();
public static void onServerStarted( ServerStartedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
ComputerMBean.registerTracker(); ComputerMBean.registerTracker();
NetworkUtils.reset();
} }
@SubscribeEvent @SubscribeEvent
public static void onServerStopped( ServerStoppedEvent event ) public static void onServerStopped( ServerStoppedEvent event )
{
resetState();
}
private static void resetState()
{ {
ComputerCraft.serverComputerRegistry.reset(); ComputerCraft.serverComputerRegistry.reset();
MainThread.reset();
WirelessNetwork.resetNetworks(); WirelessNetwork.resetNetworks();
Tracking.reset(); Tracking.reset();
NetworkUtils.reset(); NetworkUtils.reset();

View File

@ -95,12 +95,12 @@ public final class Config
{ // General computers { // General computers
computerSpaceLimit = builder computerSpaceLimit = builder
.comment( "The disk space limit for computers and turtles, in bytes" ) .comment( "The disk space limit for computers and turtles, in bytes." )
.translation( TRANSLATION_PREFIX + "computer_space_limit" ) .translation( TRANSLATION_PREFIX + "computer_space_limit" )
.define( "computer_space_limit", ComputerCraft.computerSpaceLimit ); .define( "computer_space_limit", ComputerCraft.computerSpaceLimit );
floppySpaceLimit = builder floppySpaceLimit = builder
.comment( "The disk space limit for floppy disks, in bytes" ) .comment( "The disk space limit for floppy disks, in bytes." )
.translation( TRANSLATION_PREFIX + "floppy_space_limit" ) .translation( TRANSLATION_PREFIX + "floppy_space_limit" )
.define( "floppy_space_limit", ComputerCraft.floppySpaceLimit ); .define( "floppy_space_limit", ComputerCraft.floppySpaceLimit );
@ -110,49 +110,36 @@ public final class Config
.defineInRange( "maximum_open_files", ComputerCraft.maximumFilesOpen, 0, Integer.MAX_VALUE ); .defineInRange( "maximum_open_files", ComputerCraft.maximumFilesOpen, 0, Integer.MAX_VALUE );
disableLua51Features = builder disableLua51Features = builder
.comment( "Set this to true to disable Lua 5.1 functions that will be removed in a future update. " + .comment( "Set this to true to disable Lua 5.1 functions that will be removed in a future\nupdate. Useful for ensuring forward compatibility of your programs now." )
"Useful for ensuring forward compatibility of your programs now." )
.define( "disable_lua51_features", ComputerCraft.disableLua51Features ); .define( "disable_lua51_features", ComputerCraft.disableLua51Features );
defaultComputerSettings = builder defaultComputerSettings = builder
.comment( "A comma separated list of default system settings to set on new computers. Example: " + .comment( "A comma separated list of default system settings to set on new computers.\nExample: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nwill disable all autocompletion." )
"\"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all " +
"autocompletion" )
.define( "default_computer_settings", ComputerCraft.defaultComputerSettings ); .define( "default_computer_settings", ComputerCraft.defaultComputerSettings );
logComputerErrors = builder logComputerErrors = builder
.comment( "Log exceptions thrown by peripherals and other Lua objects.\n" + .comment( "Log exceptions thrown by peripherals and other Lua objects. This makes it easier\nfor mod authors to debug problems, but may result in log spam should people use\nbuggy methods." )
"This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." )
.define( "log_computer_errors", ComputerCraft.logComputerErrors ); .define( "log_computer_errors", ComputerCraft.logComputerErrors );
commandRequireCreative = builder commandRequireCreative = builder
.comment( "Require players to be in creative mode and be opped in order to interact with command computers." + .comment( "Require players to be in creative mode and be opped in order to interact with\ncommand computers. This is the default behaviour for vanilla's Command blocks." ).define( "command_require_creative", ComputerCraft.commandRequireCreative );
"This is the default behaviour for vanilla's Command blocks." )
.define( "command_require_creative", ComputerCraft.commandRequireCreative );
} }
{ {
builder.comment( "Controls execution behaviour of computers. This is largely intended for fine-tuning " + builder.comment( "Controls execution behaviour of computers. This is largely intended for\nfine-tuning servers, and generally shouldn't need to be touched." );
"servers, and generally shouldn't need to be touched" );
builder.push( "execution" ); builder.push( "execution" );
computerThreads = builder computerThreads = builder
.comment( "Set the number of threads computers can run on. A higher number means more computers can run " + .comment( "Set the number of threads computers can run on. A higher number means more\ncomputers can run at once, but may induce lag. Please note that some mods may\nnot work with a thread count higher than 1. Use with caution." )
"at once, but may induce lag.\n" +
"Please note that some mods may not work with a thread count higher than 1. Use with caution." )
.worldRestart() .worldRestart()
.defineInRange( "computer_threads", ComputerCraft.computerThreads, 1, Integer.MAX_VALUE ); .defineInRange( "computer_threads", ComputerCraft.computerThreads, 1, Integer.MAX_VALUE );
maxMainGlobalTime = builder maxMainGlobalTime = builder
.comment( "The maximum time that can be spent executing tasks in a single tick, in milliseconds.\n" + .comment( "The maximum time that can be spent executing tasks in a single tick, in\nmilliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time." )
"Note, we will quite possibly go over this limit, as there's no way to tell how long a will take " +
"- this aims to be the upper bound of the average time." )
.defineInRange( "max_main_global_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainGlobalTime ), 1, Integer.MAX_VALUE ); .defineInRange( "max_main_global_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainGlobalTime ), 1, Integer.MAX_VALUE );
maxMainComputerTime = builder maxMainComputerTime = builder
.comment( "The ideal maximum time a computer can execute for in a tick, in milliseconds.\n" + .comment( "The ideal maximum time a computer can execute for in a tick, in milliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time." )
"Note, we will quite possibly go over this limit, as there's no way to tell how long a will take " +
"- this aims to be the upper bound of the average time." )
.defineInRange( "max_main_computer_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainComputerTime ), 1, Integer.MAX_VALUE ); .defineInRange( "max_main_computer_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainComputerTime ), 1, Integer.MAX_VALUE );
builder.pop(); builder.pop();
@ -171,17 +158,14 @@ public final class Config
.define( "websocket_enabled", ComputerCraft.httpWebsocketEnabled ); .define( "websocket_enabled", ComputerCraft.httpWebsocketEnabled );
httpRules = builder httpRules = builder
.comment( "A list of rules which control behaviour of the \"http\" API for specific domains or IPs.\n" + .comment( "A list of rules which control behaviour of the \"http\" API for specific domains or\nIPs. Each rule is an item with a 'host' to match against, and a series of\nproperties. Rules are evaluated in order, meaning earlier rules override later\nones.\nThe host may be a domain name (\"pastebin.com\"), wildcard (\"*.pastebin.com\") or\nCIDR notation (\"127.0.0.0/8\").\nIf no rules, the domain is blocked." )
"Each rule is an item with a 'host' to match against, and a series of properties. " +
"The host may be a domain name (\"pastebin.com\"),\n" +
"wildcard (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\"). If no rules, the domain is blocked." )
.defineList( "rules", Arrays.asList( .defineList( "rules", Arrays.asList(
AddressRuleConfig.makeRule( "$private", Action.DENY ), AddressRuleConfig.makeRule( "$private", Action.DENY ),
AddressRuleConfig.makeRule( "*", Action.ALLOW ) AddressRuleConfig.makeRule( "*", Action.ALLOW )
), x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule( (UnmodifiableConfig) x ) ); ), x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule( (UnmodifiableConfig) x ) );
httpMaxRequests = builder httpMaxRequests = builder
.comment( "The number of http requests a computer can make at one time. Additional requests will be queued, and sent when the running requests have finished. Set to 0 for unlimited." ) .comment( "The number of http requests a computer can make at one time. Additional requests\nwill be queued, and sent when the running requests have finished. Set to 0 for\nunlimited." )
.defineInRange( "max_requests", ComputerCraft.httpMaxRequests, 0, Integer.MAX_VALUE ); .defineInRange( "max_requests", ComputerCraft.httpMaxRequests, 0, Integer.MAX_VALUE );
httpMaxWebsockets = builder httpMaxWebsockets = builder
@ -189,15 +173,15 @@ public final class Config
.defineInRange( "max_websockets", ComputerCraft.httpMaxWebsockets, 1, Integer.MAX_VALUE ); .defineInRange( "max_websockets", ComputerCraft.httpMaxWebsockets, 1, Integer.MAX_VALUE );
builder builder
.comment( "Limits bandwidth used by computers" ) .comment( "Limits bandwidth used by computers." )
.push( "bandwidth" ); .push( "bandwidth" );
httpDownloadBandwidth = builder httpDownloadBandwidth = builder
.comment( "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s)" ) .comment( "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s)." )
.defineInRange( "global_download", ComputerCraft.httpDownloadBandwidth, 1, Integer.MAX_VALUE ); .defineInRange( "global_download", ComputerCraft.httpDownloadBandwidth, 1, Integer.MAX_VALUE );
httpUploadBandwidth = builder httpUploadBandwidth = builder
.comment( "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s)" ) .comment( "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s)." )
.defineInRange( "global_upload", ComputerCraft.httpUploadBandwidth, 1, Integer.MAX_VALUE ); .defineInRange( "global_upload", ComputerCraft.httpUploadBandwidth, 1, Integer.MAX_VALUE );
builder.pop(); builder.pop();
@ -214,33 +198,27 @@ public final class Config
.define( "command_block_enabled", ComputerCraft.enableCommandBlock ); .define( "command_block_enabled", ComputerCraft.enableCommandBlock );
modemRange = builder modemRange = builder
.comment( "The range of Wireless Modems at low altitude in clear weather, in meters" ) .comment( "The range of Wireless Modems at low altitude in clear weather, in meters." )
.defineInRange( "modem_range", ComputerCraft.modemRange, 0, MODEM_MAX_RANGE ); .defineInRange( "modem_range", ComputerCraft.modemRange, 0, MODEM_MAX_RANGE );
modemHighAltitudeRange = builder modemHighAltitudeRange = builder
.comment( "The range of Wireless Modems at maximum altitude in clear weather, in meters" ) .comment( "The range of Wireless Modems at maximum altitude in clear weather, in meters." )
.defineInRange( "modem_high_altitude_range", ComputerCraft.modemHighAltitudeRange, 0, MODEM_MAX_RANGE ); .defineInRange( "modem_high_altitude_range", ComputerCraft.modemHighAltitudeRange, 0, MODEM_MAX_RANGE );
modemRangeDuringStorm = builder modemRangeDuringStorm = builder
.comment( "The range of Wireless Modems at low altitude in stormy weather, in meters" ) .comment( "The range of Wireless Modems at low altitude in stormy weather, in meters." )
.defineInRange( "modem_range_during_storm", ComputerCraft.modemRangeDuringStorm, 0, MODEM_MAX_RANGE ); .defineInRange( "modem_range_during_storm", ComputerCraft.modemRangeDuringStorm, 0, MODEM_MAX_RANGE );
modemHighAltitudeRangeDuringStorm = builder modemHighAltitudeRangeDuringStorm = builder
.comment( "The range of Wireless Modems at maximum altitude in stormy weather, in meters" ) .comment( "The range of Wireless Modems at maximum altitude in stormy weather, in meters." )
.defineInRange( "modem_high_altitude_range_during_storm", ComputerCraft.modemHighAltitudeRangeDuringStorm, 0, MODEM_MAX_RANGE ); .defineInRange( "modem_high_altitude_range_during_storm", ComputerCraft.modemHighAltitudeRangeDuringStorm, 0, MODEM_MAX_RANGE );
maxNotesPerTick = builder maxNotesPerTick = builder
.comment( "Maximum amount of notes a speaker can play at once" ) .comment( "Maximum amount of notes a speaker can play at once." )
.defineInRange( "max_notes_per_tick", ComputerCraft.maxNotesPerTick, 1, Integer.MAX_VALUE ); .defineInRange( "max_notes_per_tick", ComputerCraft.maxNotesPerTick, 1, Integer.MAX_VALUE );
monitorBandwidth = builder monitorBandwidth = builder
.comment( "The limit to how much monitor data can be sent *per tick*. Note:\n" + .comment( "The limit to how much monitor data can be sent *per tick*. Note:\n - Bandwidth is measured before compression, so the data sent to the client is\n smaller.\n - This ignores the number of players a packet is sent to. Updating a monitor for\n one player consumes the same bandwidth limit as sending to 20.\n - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40\n monitors to be updated in a single tick.\nSet to 0 to disable." )
" - Bandwidth is measured before compression, so the data sent to the client is smaller.\n" +
" - This ignores the number of players a packet is sent to. Updating a monitor for one player consumes " +
"the same bandwidth limit as sending to 20.\n" +
" - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40 monitors to be updated " +
"in a single tick. \n" +
"Set to 0 to disable." )
.defineInRange( "monitor_bandwidth", (int) ComputerCraft.monitorBandwidth, 0, Integer.MAX_VALUE ); .defineInRange( "monitor_bandwidth", (int) ComputerCraft.monitorBandwidth, 0, Integer.MAX_VALUE );
builder.pop(); builder.pop();
@ -251,43 +229,43 @@ public final class Config
builder.push( "turtle" ); builder.push( "turtle" );
turtlesNeedFuel = builder turtlesNeedFuel = builder
.comment( "Set whether Turtles require fuel to move" ) .comment( "Set whether Turtles require fuel to move." )
.define( "need_fuel", ComputerCraft.turtlesNeedFuel ); .define( "need_fuel", ComputerCraft.turtlesNeedFuel );
turtleFuelLimit = builder turtleFuelLimit = builder
.comment( "The fuel limit for Turtles" ) .comment( "The fuel limit for Turtles." )
.defineInRange( "normal_fuel_limit", ComputerCraft.turtleFuelLimit, 0, Integer.MAX_VALUE ); .defineInRange( "normal_fuel_limit", ComputerCraft.turtleFuelLimit, 0, Integer.MAX_VALUE );
advancedTurtleFuelLimit = builder advancedTurtleFuelLimit = builder
.comment( "The fuel limit for Advanced Turtles" ) .comment( "The fuel limit for Advanced Turtles." )
.defineInRange( "advanced_fuel_limit", ComputerCraft.advancedTurtleFuelLimit, 0, Integer.MAX_VALUE ); .defineInRange( "advanced_fuel_limit", ComputerCraft.advancedTurtleFuelLimit, 0, Integer.MAX_VALUE );
turtlesObeyBlockProtection = builder turtlesObeyBlockProtection = builder
.comment( "If set to true, Turtles will be unable to build, dig, or enter protected areas (such as near the server spawn point)" ) .comment( "If set to true, Turtles will be unable to build, dig, or enter protected areas\n(such as near the server spawn point)." )
.define( "obey_block_protection", ComputerCraft.turtlesObeyBlockProtection ); .define( "obey_block_protection", ComputerCraft.turtlesObeyBlockProtection );
turtlesCanPush = builder turtlesCanPush = builder
.comment( "If set to true, Turtles will push entities out of the way instead of stopping if there is space to do so" ) .comment( "If set to true, Turtles will push entities out of the way instead of stopping if\n" +
"there is space to do so." )
.define( "can_push", ComputerCraft.turtlesCanPush ); .define( "can_push", ComputerCraft.turtlesCanPush );
builder.pop(); builder.pop();
} }
{ {
builder.comment( "Configure the size of various computer's terminals.\n" + builder.comment( "Configure the size of various computer's terminals.\nLarger terminals require more bandwidth, so use with care." ).push( "term_sizes" );
"Larger terminals require more bandwidth, so use with care." ).push( "term_sizes" );
builder.comment( "Terminal size of computers" ).push( "computer" ); builder.comment( "Terminal size of computers." ).push( "computer" );
computerTermWidth = builder.defineInRange( "width", ComputerCraft.computerTermWidth, 1, 255 ); computerTermWidth = builder.defineInRange( "width", ComputerCraft.computerTermWidth, 1, 255 );
computerTermHeight = builder.defineInRange( "height", ComputerCraft.computerTermHeight, 1, 255 ); computerTermHeight = builder.defineInRange( "height", ComputerCraft.computerTermHeight, 1, 255 );
builder.pop(); builder.pop();
builder.comment( "Terminal size of pocket computers" ).push( "pocket_computer" ); builder.comment( "Terminal size of pocket computers." ).push( "pocket_computer" );
pocketTermWidth = builder.defineInRange( "width", ComputerCraft.pocketTermWidth, 1, 255 ); pocketTermWidth = builder.defineInRange( "width", ComputerCraft.pocketTermWidth, 1, 255 );
pocketTermHeight = builder.defineInRange( "height", ComputerCraft.pocketTermHeight, 1, 255 ); pocketTermHeight = builder.defineInRange( "height", ComputerCraft.pocketTermHeight, 1, 255 );
builder.pop(); builder.pop();
builder.comment( "Maximum size of monitors (in blocks)" ).push( "monitor" ); builder.comment( "Maximum size of monitors (in blocks)." ).push( "monitor" );
monitorWidth = builder.defineInRange( "width", ComputerCraft.monitorWidth, 1, 32 ); monitorWidth = builder.defineInRange( "width", ComputerCraft.monitorWidth, 1, 32 );
monitorHeight = builder.defineInRange( "height", ComputerCraft.monitorHeight, 1, 32 ); monitorHeight = builder.defineInRange( "height", ComputerCraft.monitorHeight, 1, 32 );
builder.pop(); builder.pop();
@ -299,12 +277,10 @@ public final class Config
Builder clientBuilder = new Builder(); Builder clientBuilder = new Builder();
monitorRenderer = clientBuilder monitorRenderer = clientBuilder
.comment( "The renderer to use for monitors. Generally this should be kept at \"best\" - if " + .comment( "The renderer to use for monitors. Generally this should be kept at \"best\" - if\nmonitors have performance issues, you may wish to experiment with alternative\nrenderers." )
"monitors have performance issues, you may wish to experiment with alternative renderers." )
.defineEnum( "monitor_renderer", MonitorRenderer.BEST ); .defineEnum( "monitor_renderer", MonitorRenderer.BEST );
monitorDistance = clientBuilder monitorDistance = clientBuilder
.comment( "The maximum distance monitors will render at. This defaults to the standard tile entity limit, " + .comment( "The maximum distance monitors will render at. This defaults to the standard tile\nentity limit, but may be extended if you wish to build larger monitors." )
"but may be extended if you wish to build larger monitors." )
.defineInRange( "monitor_distance", 64, 16, 1024 ); .defineInRange( "monitor_distance", 64, 16, 1024 );
clientSpec = clientBuilder.build(); clientSpec = clientBuilder.build();
} }

View File

@ -141,9 +141,10 @@ public final class CommandComputerCraft
.then( command( "shutdown" ) .then( command( "shutdown" )
.requires( UserLevel.OWNER_OP ) .requires( UserLevel.OWNER_OP )
.argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() )
.executes( ( context, computers ) -> { .executes( ( context, computerSelectors ) -> {
int shutdown = 0; int shutdown = 0;
for( ServerComputer computer : unwrap( context.getSource(), computers ) ) Set<ServerComputer> computers = unwrap( context.getSource(), computerSelectors );
for( ServerComputer computer : computers )
{ {
if( computer.isOn() ) shutdown++; if( computer.isOn() ) shutdown++;
computer.shutdown(); computer.shutdown();
@ -155,9 +156,10 @@ public final class CommandComputerCraft
.then( command( "turn-on" ) .then( command( "turn-on" )
.requires( UserLevel.OWNER_OP ) .requires( UserLevel.OWNER_OP )
.argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() )
.executes( ( context, computers ) -> { .executes( ( context, computerSelectors ) -> {
int on = 0; int on = 0;
for( ServerComputer computer : unwrap( context.getSource(), computers ) ) Set<ServerComputer> computers = unwrap( context.getSource(), computerSelectors );
for( ServerComputer computer : computers )
{ {
if( !computer.isOn() ) on++; if( !computer.isOn() ) on++;
computer.turnOn(); computer.turnOn();

View File

@ -12,6 +12,8 @@ import net.minecraft.core.Direction;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateDefinition;
@ -50,6 +52,22 @@ public class BlockComputer<T extends TileComputer> extends BlockComputerBase<T>
return defaultBlockState().setValue( FACING, placement.getHorizontalDirection().getOpposite() ); return defaultBlockState().setValue( FACING, placement.getHorizontalDirection().getOpposite() );
} }
@Nonnull
@Override
@Deprecated
public BlockState mirror( BlockState state, Mirror mirrorIn )
{
return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) );
}
@Nonnull
@Override
@Deprecated
public BlockState rotate( BlockState state, Rotation rot )
{
return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) );
}
@Nonnull @Nonnull
@Override @Override
protected ItemStack getItem( TileComputerBase tile ) protected ItemStack getItem( TileComputerBase tile )

View File

@ -0,0 +1,39 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.integration;
/**
* Detect whether Optifine is installed.
*/
public final class Optifine
{
private static final boolean LOADED;
static
{
boolean loaded;
try
{
Class.forName( "optifine.Installer", false, Optifine.class.getClassLoader() );
loaded = true;
}
catch( ReflectiveOperationException | LinkageError ignore )
{
loaded = false;
}
LOADED = loaded;
}
private Optifine()
{
}
public static boolean isLoaded()
{
return LOADED;
}
}

View File

@ -60,7 +60,7 @@ class RecipeResolver implements IRecipeManagerPlugin
UpgradeInfo info = new UpgradeInfo( stack, upgrade ); UpgradeInfo info = new UpgradeInfo( stack, upgrade );
upgradeItemLookup.computeIfAbsent( stack.getItem(), k -> new ArrayList<>( 1 ) ).add( info ); upgradeItemLookup.computeIfAbsent( stack.getItem(), k -> new ArrayList<>( 1 ) ).add( info );
turtleUpgrades.add( info ); turtleUpgrades.add( info );
}; }
for( IPocketUpgrade upgrade : PocketUpgrades.instance().getUpgrades() ) for( IPocketUpgrade upgrade : PocketUpgrades.instance().getUpgrades() )
{ {

View File

@ -18,6 +18,8 @@ import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock; import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
@ -52,6 +54,22 @@ public class BlockDiskDrive extends BlockGeneric
properties.add( FACING, STATE ); properties.add( FACING, STATE );
} }
@Nonnull
@Override
@Deprecated
public BlockState mirror( BlockState state, Mirror mirrorIn )
{
return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) );
}
@Nonnull
@Override
@Deprecated
public BlockState rotate( BlockState state, Rotation rot )
{
return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) );
}
@Nullable @Nullable
@Override @Override
public BlockState getStateForPlacement( BlockPlaceContext placement ) public BlockState getStateForPlacement( BlockPlaceContext placement )

View File

@ -15,6 +15,8 @@ import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.SimpleWaterloggedBlock; import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
@ -96,4 +98,20 @@ public class BlockWirelessModem extends BlockGeneric implements SimpleWaterlogge
.setValue( FACING, placement.getClickedFace().getOpposite() ) .setValue( FACING, placement.getClickedFace().getOpposite() )
.setValue( WATERLOGGED, getFluidStateForPlacement( placement ) ); .setValue( WATERLOGGED, getFluidStateForPlacement( placement ) );
} }
@Nonnull
@Override
@Deprecated
public BlockState mirror( BlockState state, Mirror mirrorIn )
{
return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) );
}
@Nonnull
@Override
@Deprecated
public BlockState rotate( BlockState state, Rotation rot )
{
return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) );
}
} }

View File

@ -13,6 +13,8 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
@ -51,6 +53,22 @@ public class BlockMonitor extends BlockGeneric
builder.add( ORIENTATION, FACING, STATE ); builder.add( ORIENTATION, FACING, STATE );
} }
@Nonnull
@Override
@Deprecated
public BlockState mirror( BlockState state, Mirror mirrorIn )
{
return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) );
}
@Nonnull
@Override
@Deprecated
public BlockState rotate( BlockState state, Rotation rot )
{
return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) );
}
@Override @Override
@Nullable @Nullable
public BlockState getStateForPlacement( BlockPlaceContext context ) public BlockState getStateForPlacement( BlockPlaceContext context )

View File

@ -7,7 +7,7 @@ package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.render.TileEntityMonitorRenderer; import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import net.minecraftforge.fml.ModList; import dan200.computercraft.shared.integration.Optifine;
import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -60,7 +60,7 @@ public enum MonitorRenderer
return VBO; return VBO;
} }
if( ModList.get().isLoaded( "optifine" ) ) if( Optifine.isLoaded() )
{ {
ComputerCraft.log.warn( "Optifine is loaded, assuming shaders are being used. Falling back to VBO monitor renderer." ); ComputerCraft.log.warn( "Optifine is loaded, assuming shaders are being used. Falling back to VBO monitor renderer." );
return VBO; return VBO;

View File

@ -17,6 +17,8 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateDefinition;
@ -48,6 +50,22 @@ public class BlockPrinter extends BlockGeneric
properties.add( FACING, TOP, BOTTOM ); properties.add( FACING, TOP, BOTTOM );
} }
@Nonnull
@Override
@Deprecated
public BlockState mirror( BlockState state, Mirror mirrorIn )
{
return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) );
}
@Nonnull
@Override
@Deprecated
public BlockState rotate( BlockState state, Rotation rot )
{
return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) );
}
@Nullable @Nullable
@Override @Override
public BlockState getStateForPlacement( BlockPlaceContext placement ) public BlockState getStateForPlacement( BlockPlaceContext placement )

View File

@ -12,6 +12,8 @@ import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock; import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
@ -42,6 +44,22 @@ public class BlockSpeaker extends BlockGeneric
properties.add( FACING ); properties.add( FACING );
} }
@Nonnull
@Override
@Deprecated
public BlockState mirror( BlockState state, Mirror mirrorIn )
{
return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) );
}
@Nonnull
@Override
@Deprecated
public BlockState rotate( BlockState state, Rotation rot )
{
return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) );
}
@Nullable @Nullable
@Override @Override
public BlockState getStateForPlacement( BlockPlaceContext placement ) public BlockState getStateForPlacement( BlockPlaceContext placement )

View File

@ -302,7 +302,7 @@ public abstract class SpeakerPeripheral implements IPeripheral
* computer is lagging. * computer is lagging.
* ::: * :::
* *
* {@literal @}{speaker_audio} provides a more complete guide in to using speakers * {@literal @}{speaker_audio} provides a more complete guide to using speakers
* *
* @param context The Lua context. * @param context The Lua context.
* @param audio The audio data to play. * @param audio The audio data to play.

View File

@ -11,6 +11,8 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -73,6 +75,7 @@ public record SpeakerPosition(@Nullable Level level, @Nonnull Vec3 position, @Nu
} }
@Nonnull @Nonnull
@OnlyIn( Dist.CLIENT )
public SpeakerPosition reify() public SpeakerPosition reify()
{ {
Minecraft minecraft = Minecraft.getInstance(); Minecraft minecraft = Minecraft.getInstance();

View File

@ -26,10 +26,7 @@ import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.BaseEntityBlock; import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
@ -76,6 +73,22 @@ public class BlockTurtle extends BlockComputerBase<TileTurtle> implements Simple
builder.add( FACING, WATERLOGGED ); builder.add( FACING, WATERLOGGED );
} }
@Nonnull
@Override
@Deprecated
public BlockState mirror( BlockState state, Mirror mirrorIn )
{
return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) );
}
@Nonnull
@Override
@Deprecated
public BlockState rotate( BlockState state, Rotation rot )
{
return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) );
}
@Nonnull @Nonnull
@Override @Override
@Deprecated @Deprecated

View File

@ -1,12 +1,13 @@
--[[- The Colors API allows you to manipulate sets of colors. --[[- Constants and functions for colour values, suitable for working with
@{term} and @{redstone}.
This is useful in conjunction with Bundled Cables from the RedPower mod, RedNet This is useful in conjunction with @{redstone.setBundledOutput|Bundled Cables}
Cables from the MineFactory Reloaded mod, and colors on Advanced Computers and from mods like Project Red, and @{term.setTextColour|colors on Advanced
Advanced Monitors. Computers and Advanced Monitors}.
For the non-American English version just replace @{colors} with @{colours} and For the non-American English version just replace @{colors} with @{colours}.
it will use the other API, colours which is exactly the same, except in British This alternative API is exactly the same, except the colours use British English
English (e.g. @{colors.gray} is spelt @{colours.grey}). (e.g. @{colors.gray} is spelt @{colours.grey}).
On basic terminals (such as the Computer and Monitor), all the colors are On basic terminals (such as the Computer and Monitor), all the colors are
converted to grayscale. This means you can still use all 16 colors on the converted to grayscale. This means you can still use all 16 colors on the
@ -16,7 +17,7 @@ terminal supports color by using the function @{term.isColor}.
Grayscale colors are calculated by taking the average of the three components, Grayscale colors are calculated by taking the average of the three components,
i.e. `(red + green + blue) / 3`. i.e. `(red + green + blue) / 3`.
<table class="pretty-table"> <table>
<thead> <thead>
<tr><th colspan="8" align="center">Default Colors</th></tr> <tr><th colspan="8" align="center">Default Colors</th></tr>
<tr> <tr>

View File

@ -1,4 +1,4 @@
--- Colours for lovers of British spelling. --- An alternative version of @{colors} for lovers of British spelling.
-- --
-- @see colors -- @see colors
-- @module colours -- @module colours

View File

@ -1,21 +1,26 @@
--- The commands API allows your system to directly execute [Minecraft --[[- Execute [Minecraft commands][mc] and gather data from the results from
-- commands][mc] and gather data from the results. a command computer.
--
-- While one may use @{commands.exec} directly to execute a command, the
-- commands API also provides helper methods to execute every command. For
-- instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`.
--
-- @{commands.async} provides a similar interface to execute asynchronous
-- commands. `commands.async.say("Hi!")` is equivalent to
-- `commands.execAsync("Hi!")`.
--
-- [mc]: https://minecraft.gamepedia.com/Commands
--
-- @module commands
-- @usage Set the block above this computer to stone:
--
-- commands.setblock("~", "~1", "~", "minecraft:stone")
:::note
This API is only available on Command computers. It is not accessible to normal
players.
:::
While one may use @{commands.exec} directly to execute a command, the
commands API also provides helper methods to execute every command. For
instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`.
@{commands.async} provides a similar interface to execute asynchronous
commands. `commands.async.say("Hi!")` is equivalent to
`commands.execAsync("say Hi!")`.
[mc]: https://minecraft.gamepedia.com/Commands
@module commands
@usage Set the block above this computer to stone:
commands.setblock("~", "~1", "~", "minecraft:stone")
]]
if not commands then if not commands then
error("Cannot load command API on normal computer", 2) error("Cannot load command API on normal computer", 2)
end end

View File

@ -1,4 +1,4 @@
--[[- The Disk API allows you to interact with disk drives. --[[- Interact with disk drives.
These functions can operate on locally attached or remote disk drives. To use a These functions can operate on locally attached or remote disk drives. To use a
locally attached drive, specify side as one of the six sides (e.g. `left`); to locally attached drive, specify side as one of the six sides (e.g. `left`); to

View File

@ -1,5 +1,5 @@
--[[- The GPS API provides a method for turtles and computers to retrieve their --[[- Use @{modem|modems} to locate the position of the current turtle or
own locations. computers.
It broadcasts a PING message over @{rednet} and wait for responses. In order for It broadcasts a PING message over @{rednet} and wait for responses. In order for
this system to work, there must be at least 4 computers used as gps hosts which this system to work, there must be at least 4 computers used as gps hosts which

View File

@ -1,4 +1,4 @@
--- Provides an API to read help files. --- Find help files on the current computer.
-- --
-- @module help -- @module help
-- @since 1.2 -- @since 1.2

View File

@ -1,5 +1,4 @@
--- The Keys API provides a table of numerical codes corresponding to keyboard --- Constants for all keyboard "key codes", as queued by the @{key} event.
-- keys, suitable for decoding key events.
-- --
-- These values are not guaranteed to remain the same between versions. It is -- These values are not guaranteed to remain the same between versions. It is
-- recommended that you use the constants provided by this file, rather than -- recommended that you use the constants provided by this file, rather than

View File

@ -1,5 +1,5 @@
--- An API for advanced systems which can draw pixels and lines, load and draw --- Utilities for drawing more complex graphics, such as pixels, lines and
-- image files. You can use the `colors` API for easier color manipulation. -- images.
-- --
-- @module paintutils -- @module paintutils
-- @since 1.45 -- @since 1.45

View File

@ -1,8 +1,8 @@
--[[- Provides a simple implementation of multitasking. --[[- A simple way to run several functions at once.
Functions are not actually executed simultaniously, but rather this API will Functions are not actually executed simultaniously, but rather this API will
automatically switch between them whenever they yield (eg whenever they call automatically switch between them whenever they yield (e.g. whenever they call
@{coroutine.yield}, or functions that call that - eg @{os.pullEvent} - or @{coroutine.yield}, or functions that call that - such as @{os.pullEvent} - or
functions that call that, etc - basically, anything that causes the function functions that call that, etc - basically, anything that causes the function
to "pause"). to "pause").
@ -12,6 +12,27 @@ script to pause - eg @{os.sleep}, @{rednet.receive}, most of the @{turtle} API,
etc) can safely be used in one without affecting the event queue accessed by etc) can safely be used in one without affecting the event queue accessed by
the other. the other.
:::caution
When using this API, be careful to pass the functions you want to run in
parallel, and _not_ the result of calling those functions.
For instance, the following is correct:
```lua
local function do_sleep() sleep(1) end
parallel.waitForAny(do_sleep, rednet.receive)
```
but the following is **NOT**:
```lua
local function do_sleep() sleep(1) end
parallel.waitForAny(do_sleep(), rednet.receive)
```
:::
@module parallel @module parallel
@since 1.2 @since 1.2
]] ]]

View File

@ -1,4 +1,6 @@
--[[- Peripherals are blocks (or turtle and pocket computer upgrades) which can --[[- Find and control peripherals attached to this computer.
Peripherals are blocks (or turtle and pocket computer upgrades) which can
be controlled by a computer. For instance, the @{speaker} peripheral allows a be controlled by a computer. For instance, the @{speaker} peripheral allows a
computer to play music and the @{monitor} peripheral allows you to display text computer to play music and the @{monitor} peripheral allows you to display text
in the world. in the world.

View File

@ -1,6 +1,6 @@
--[[- The Rednet API allows computers to communicate between each other by using --[[- Communicate with other computers by using @{modem|modems}. @{rednet}
@{modem|modems}. It provides a layer of abstraction on top of the main @{modem} provides a layer of abstraction on top of the main @{modem} peripheral, making
peripheral, making it slightly easier to use. it slightly easier to use.
## Basic usage ## Basic usage
In order to send a message between two computers, each computer must have a In order to send a message between two computers, each computer must have a

View File

@ -1,5 +1,4 @@
--- The settings API allows to store values and save them to a file for --- Read and write configuration options for CraftOS and your programs.
-- persistent configurations for CraftOS and your programs.
-- --
-- By default, the settings API will load its configuration from the -- By default, the settings API will load its configuration from the
-- `/.settings` file. One can then use @{settings.save} to update the file. -- `/.settings` file. One can then use @{settings.save} to update the file.

View File

@ -1,7 +1,4 @@
--- The Terminal API provides functions for writing text to the terminal and --- @module term
-- monitors, and drawing ASCII graphics.
--
-- @module term
local expect = dofile("rom/modules/main/cc/expect.lua").expect local expect = dofile("rom/modules/main/cc/expect.lua").expect

View File

@ -1,5 +1,4 @@
--- The @{textutils} API provides helpful utilities for formatting and --- Helpful utilities for formatting and manipulating strings.
-- manipulating strings.
-- --
-- @module textutils -- @module textutils
-- @since 1.2 -- @since 1.2
@ -632,7 +631,13 @@ do
end end
if c == "" then return expected(pos, c, "']'") end if c == "" then return expected(pos, c, "']'") end
if c == "]" then return empty_json_array, pos + 1 end if c == "]" then
if opts.parse_empty_array ~= false then
return empty_json_array, pos + 1
else
return {}, pos + 1
end
end
while true do while true do
n, arr[n], pos = n + 1, decode_impl(str, pos, opts) n, arr[n], pos = n + 1, decode_impl(str, pos, opts)
@ -653,32 +658,51 @@ do
error_at(pos, "Unexpected character %q.", c) error_at(pos, "Unexpected character %q.", c)
end end
--- Converts a serialised JSON string back into a reassembled Lua object. --[[- Converts a serialised JSON string back into a reassembled Lua object.
--
-- This may be used with @{textutils.serializeJSON}, or when communicating This may be used with @{textutils.serializeJSON}, or when communicating
-- with command blocks or web APIs. with command blocks or web APIs.
--
-- @tparam string s The serialised string to deserialise. If a `null` value is encountered, it is converted into `nil`. It can be converted
-- @tparam[opt] { nbt_style? = boolean, parse_null? = boolean } options into @{textutils.json_null} with the `parse_null` option.
-- Options which control how this JSON object is parsed.
-- If an empty array is encountered, it is converted into @{textutils.empty_json_array}.
-- - `nbt_style`: When true, this will accept [stringified NBT][nbt] strings, It can be converted into a new empty table with the `parse_empty_array` option.
-- as produced by many commands.
-- - `parse_null`: When true, `null` will be parsed as @{json_null}, rather @tparam string s The serialised string to deserialise.
-- than `nil`. @tparam[opt] { nbt_style? = boolean, parse_null? = boolean, parse_empty_array? = boolean } options
-- Options which control how this JSON object is parsed.
-- [nbt]: https://minecraft.gamepedia.com/NBT_format
-- @return[1] The deserialised object - `nbt_style`: When true, this will accept [stringified NBT][nbt] strings,
-- @treturn[2] nil If the object could not be deserialised. as produced by many commands.
-- @treturn string A message describing why the JSON string is invalid. - `parse_null`: When true, `null` will be parsed as @{json_null}, rather than
-- @since 1.87.0 `nil`.
- `parse_empty_array`: When false, empty arrays will be parsed as a new table.
By default (or when this value is true), they are parsed as @{empty_json_array}.
[nbt]: https://minecraft.gamepedia.com/NBT_format
@return[1] The deserialised object
@treturn[2] nil If the object could not be deserialised.
@treturn string A message describing why the JSON string is invalid.
@since 1.87.0
@see textutils.json_null Use to serialize a JSON `null` value.
@see textutils.empty_json_array Use to serialize a JSON empty array.
@usage Unserialise a basic JSON object
textutils.unserialiseJSON('{"name": "Steve", "age": null}')
@usage Unserialise a basic JSON object, returning null values as @{json_null}.
textutils.unserialiseJSON('{"name": "Steve", "age": null}', { parse_null = true })
]]
unserialise_json = function(s, options) unserialise_json = function(s, options)
expect(1, s, "string") expect(1, s, "string")
expect(2, options, "table", "nil") expect(2, options, "table", "nil")
if options then if options then
field(options, "nbt_style", "boolean", "nil") field(options, "nbt_style", "boolean", "nil")
field(options, "nbt_style", "boolean", "nil") field(options, "parse_null", "boolean", "nil")
field(options, "parse_empty_array", "boolean", "nil")
else else
options = {} options = {}
end end
@ -785,6 +809,8 @@ unserialise = unserialize -- GB version
-- times. -- times.
-- @usage textutils.serializeJSON({ values = { 1, "2", true } }) -- @usage textutils.serializeJSON({ values = { 1, "2", true } })
-- @since 1.7 -- @since 1.7
-- @see textutils.json_null Use to serialize a JSON `null` value.
-- @see textutils.empty_json_array Use to serialize a JSON empty array.
function serializeJSON(t, bNBTStyle) function serializeJSON(t, bNBTStyle)
expect(1, t, "table", "string", "number", "boolean") expect(1, t, "table", "string", "number", "boolean")
expect(2, bNBTStyle, "boolean", "nil") expect(2, bNBTStyle, "boolean", "nil")

View File

@ -1,4 +1,6 @@
--- The vector API provides methods to create and manipulate vectors. --- A basic 3D vector type and some common vector operations. This may be useful
-- when working with coordinates in Minecraft's world (such as those from the
-- @{gps} API).
-- --
-- An introduction to vectors can be found on [Wikipedia][wiki]. -- An introduction to vectors can be found on [Wikipedia][wiki].
-- --

View File

@ -1,32 +1,33 @@
--- The Window API allows easy definition of spaces within the display that can --[[- A @{term.Redirect|terminal redirect} occupying a smaller area of an
-- be written/drawn to, then later redrawn/repositioned/etc as need be. The API existing terminal. This allows for easy definition of spaces within the display
-- itself contains only one function, @{window.create}, which returns the that can be written/drawn to, then later redrawn/repositioned/etc as need
-- windows themselves. be. The API itself contains only one function, @{window.create}, which returns
-- the windows themselves.
-- Windows are considered terminal objects - as such, they have access to nearly
-- all the commands in the term API (plus a few extras of their own, listed Windows are considered terminal objects - as such, they have access to nearly
-- within said API) and are valid targets to redirect to. all the commands in the term API (plus a few extras of their own, listed within
-- said API) and are valid targets to redirect to.
-- Each window has a "parent" terminal object, which can be the computer's own
-- display, a monitor, another window or even other, user-defined terminal Each window has a "parent" terminal object, which can be the computer's own
-- objects. Whenever a window is rendered to, the actual screen-writing is display, a monitor, another window or even other, user-defined terminal
-- performed via that parent (or, if that has one too, then that parent, and so objects. Whenever a window is rendered to, the actual screen-writing is
-- forth). Bear in mind that the cursor of a window's parent will hence be moved performed via that parent (or, if that has one too, then that parent, and so
-- around etc when writing a given child window. forth). Bear in mind that the cursor of a window's parent will hence be moved
-- around etc when writing a given child window.
-- Windows retain a memory of everything rendered "through" them (hence acting
-- as display buffers), and if the parent's display is wiped, the window's Windows retain a memory of everything rendered "through" them (hence acting as
-- content can be easily redrawn later. A window may also be flagged as display buffers), and if the parent's display is wiped, the window's content can
-- invisible, preventing any changes to it from being rendered until it's be easily redrawn later. A window may also be flagged as invisible, preventing
-- flagged as visible once more. any changes to it from being rendered until it's flagged as visible once more.
--
-- A parent terminal object may have multiple children assigned to it, and A parent terminal object may have multiple children assigned to it, and windows
-- windows may overlap. For example, the Multishell system functions by may overlap. For example, the Multishell system functions by assigning each tab
-- assigning each tab a window covering the screen, each using the starting a window covering the screen, each using the starting terminal display as its
-- terminal display as its parent, and only one of which is visible at a time. parent, and only one of which is visible at a time.
--
-- @module window @module window
-- @since 1.6 @since 1.6
]]
local expect = dofile("rom/modules/main/cc/expect.lua").expect local expect = dofile("rom/modules/main/cc/expect.lua").expect

View File

@ -1,5 +1,5 @@
--[[- --[[-
Provides utilities for converting between streams of DFPWM audio data and a list of amplitudes. Convert between streams of DFPWM audio data and a list of amplitudes.
DFPWM (Dynamic Filter Pulse Width Modulation) is an audio codec designed by GreaseMonkey. It's a relatively compact DFPWM (Dynamic Filter Pulse Width Modulation) is an audio codec designed by GreaseMonkey. It's a relatively compact
format compared to raw PCM data, only using 1 bit per sample, but is simple enough to simple enough to encode and decode format compared to raw PCM data, only using 1 bit per sample, but is simple enough to simple enough to encode and decode
@ -18,11 +18,12 @@ for each one you write.
## Converting audio to DFPWM ## Converting audio to DFPWM
DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it. DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it.
Instead, you can convert audio files online using [music.madefor.cc] or with the [LionRay Wav Converter][LionRay] Java Instead, you can convert audio files online using [music.madefor.cc], the [LionRay Wav Converter][LionRay] Java
application. application or development builds of [FFmpeg].
[music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked" [music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked"
[LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter " [LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter "
[FFmpeg]: https://ffmpeg.org "FFmpeg command-line audio manipulation library"
@see guide!speaker_audio Gives a more general introduction to audio processing and the speaker. @see guide!speaker_audio Gives a more general introduction to audio processing and the speaker.
@see speaker.playAudio To play the decoded audio data. @see speaker.playAudio To play the decoded audio data.

View File

@ -1,8 +1,8 @@
--- Provides utilities for working with "nft" images. --- Read and draw nbt ("Nitrogen Fingers Text") images.
-- --
-- nft ("Nitrogen Fingers Text") is a file format for drawing basic images. -- nft ("Nitrogen Fingers Text") is a file format for drawing basic images.
-- Unlike the images that @{paintutils.parseImage} uses, nft supports coloured -- Unlike the images that @{paintutils.parseImage} uses, nft supports coloured
-- text. -- text as well as simple coloured pixels.
-- --
-- @module cc.image.nft -- @module cc.image.nft
-- @since 1.90.0 -- @since 1.90.0

View File

@ -1,5 +1,5 @@
--[[- Provides a "pretty printer", for rendering data structures in an --[[- A pretty printer for rendering data structures in an aesthetically
aesthetically pleasing manner. pleasing manner.
In order to display something using @{cc.pretty}, you build up a series of In order to display something using @{cc.pretty}, you build up a series of
@{Doc|documents}. These behave a little bit like strings; you can concatenate @{Doc|documents}. These behave a little bit like strings; you can concatenate

View File

@ -1,5 +1,5 @@
--[[- This provides a pure Lua implementation of the builtin @{require} function --[[- A pure Lua implementation of the builtin @{require} function and
and @{package} library. @{package} library.
Generally you do not need to use this module - it is injected into the every Generally you do not need to use this module - it is injected into the every
program's environment. However, it may be useful when building a custom shell or program's environment. However, it may be useful when building a custom shell or

View File

@ -431,7 +431,7 @@ local tMenuFuncs = {
file.write(runHandler:format(sTitle, table.concat(tLines, "\n"), "@" .. fs.getName(sPath))) file.write(runHandler:format(sTitle, table.concat(tLines, "\n"), "@" .. fs.getName(sPath)))
end) end)
if ok then if ok then
local nTask = shell.openTab(sTempPath) local nTask = shell.openTab("/" .. sTempPath)
if nTask then if nTask then
shell.switchTab(nTask) shell.switchTab(nTask)
else else

View File

@ -18,7 +18,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import static dan200.computercraft.ContramapMatcher.contramap; import static dan200.computercraft.support.ContramapMatcher.contramap;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;

View File

@ -0,0 +1,98 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.lua.MachineResult;
import dan200.computercraft.support.ConcurrentHelpers;
import dan200.computercraft.support.IsolatedRunner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.closeTo;
import static org.junit.jupiter.api.Assertions.*;
@Timeout( value = 15 )
@ExtendWith( IsolatedRunner.class )
@Execution( ExecutionMode.CONCURRENT )
public class ComputerThreadTest
{
@Test
public void testSoftAbort() throws Exception
{
Computer computer = FakeComputerManager.create();
FakeComputerManager.enqueue( computer, timeout -> {
assertFalse( timeout.isSoftAborted(), "Should not start soft-aborted" );
long delay = ConcurrentHelpers.waitUntil( timeout::isSoftAborted );
assertThat( "Should be soft aborted", delay * 1e-9, closeTo( 7, 0.5 ) );
ComputerCraft.log.info( "Slept for {}", delay );
computer.shutdown();
return MachineResult.OK;
} );
FakeComputerManager.startAndWait( computer );
}
@Test
public void testHardAbort() throws Exception
{
Computer computer = FakeComputerManager.create();
FakeComputerManager.enqueue( computer, timeout -> {
assertFalse( timeout.isHardAborted(), "Should not start soft-aborted" );
assertThrows( InterruptedException.class, () -> Thread.sleep( 11_000 ), "Sleep should be hard aborted" );
assertTrue( timeout.isHardAborted(), "Thread should be hard aborted" );
computer.shutdown();
return MachineResult.OK;
} );
FakeComputerManager.startAndWait( computer );
}
@Test
public void testNoPauseIfNoOtherMachines() throws Exception
{
Computer computer = FakeComputerManager.create();
FakeComputerManager.enqueue( computer, timeout -> {
boolean didPause = ConcurrentHelpers.waitUntil( timeout::isPaused, 5, TimeUnit.SECONDS );
assertFalse( didPause, "Machine shouldn't have paused within 5s" );
computer.shutdown();
return MachineResult.OK;
} );
FakeComputerManager.startAndWait( computer );
}
@Test
public void testPauseIfSomeOtherMachine() throws Exception
{
Computer computer = FakeComputerManager.create();
FakeComputerManager.enqueue( computer, timeout -> {
long budget = ComputerThread.scaledPeriod();
assertEquals( budget, TimeUnit.MILLISECONDS.toNanos( 25 ), "Budget should be 25ms" );
long delay = ConcurrentHelpers.waitUntil( timeout::isPaused );
assertThat( "Paused within 25ms", delay * 1e-9, closeTo( 0.03, 0.015 ) );
computer.shutdown();
return MachineResult.OK;
} );
FakeComputerManager.createLoopingComputer();
FakeComputerManager.startAndWait( computer );
}
}

View File

@ -0,0 +1,219 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. 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.core.lua.ILuaMachine;
import dan200.computercraft.core.lua.MachineResult;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.support.IsolatedRunner;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Creates "fake" computers, which just run user-defined tasks rather than Lua code.
*
* Note, this will clobber some parts of the global state. It's recommended you use this inside an {@link IsolatedRunner}.
*/
public class FakeComputerManager
{
interface Task
{
MachineResult run( TimeoutState state ) throws Exception;
}
private static final Map<Computer, Queue<Task>> machines = new HashMap<>();
private static final Lock errorLock = new ReentrantLock();
private static final Condition hasError = errorLock.newCondition();
private static volatile Throwable error;
static
{
ComputerExecutor.luaFactory = ( computer, timeout ) -> new DummyLuaMachine( timeout, machines.get( computer ) );
}
/**
* Create a new computer which pulls from our task queue.
*
* @return The computer. This will not be started yet, you must call {@link Computer#turnOn()} and
* {@link Computer#tick()} to do so.
*/
@Nonnull
public static Computer create()
{
Computer computer = new Computer( new BasicEnvironment(), new Terminal( 51, 19 ), 0 );
machines.put( computer, new ConcurrentLinkedQueue<>() );
return computer;
}
/**
* Create and start a new computer which loops forever.
*/
public static void createLoopingComputer()
{
Computer computer = create();
enqueueForever( computer, t -> {
Thread.sleep( 100 );
return MachineResult.OK;
} );
computer.turnOn();
computer.tick();
}
/**
* Enqueue a task on a computer.
*
* @param computer The computer to enqueue the work on.
* @param task The task to run.
*/
public static void enqueue( @Nonnull Computer computer, @Nonnull Task task )
{
machines.get( computer ).offer( task );
}
/**
* Enqueue a repeated task on a computer. This is automatically requeued when the task finishes, meaning the task
* queue is never empty.
*
* @param computer The computer to enqueue the work on.
* @param task The task to run.
*/
private static void enqueueForever( @Nonnull Computer computer, @Nonnull Task task )
{
machines.get( computer ).offer( t -> {
MachineResult result = task.run( t );
enqueueForever( computer, task );
computer.queueEvent( "some_event", null );
return result;
} );
}
/**
* Sleep for a given period, immediately propagating any exceptions thrown by a computer.
*
* @param delay The duration to sleep for.
* @param unit The time unit the duration is measured in.
* @throws Exception An exception thrown by a running computer.
*/
public static void sleep( long delay, TimeUnit unit ) throws Exception
{
errorLock.lock();
try
{
rethrowIfNeeded();
if( hasError.await( delay, unit ) ) rethrowIfNeeded();
}
finally
{
errorLock.unlock();
}
}
/**
* Start a computer and wait for it to finish.
*
* @param computer The computer to wait for.
* @throws Exception An exception thrown by a running computer.
*/
public static void startAndWait( Computer computer ) throws Exception
{
computer.turnOn();
computer.tick();
do
{
sleep( 100, TimeUnit.MILLISECONDS );
} while( ComputerThread.hasPendingWork() || computer.isOn() );
rethrowIfNeeded();
}
private static void rethrowIfNeeded() throws Exception
{
if( error == null ) return;
if( error instanceof Exception ) throw (Exception) error;
if( error instanceof Error ) throw (Error) error;
rethrow( error );
}
@SuppressWarnings( "unchecked" )
private static <T extends Throwable> void rethrow( Throwable e ) throws T
{
throw (T) e;
}
private static class DummyLuaMachine implements ILuaMachine
{
private final TimeoutState state;
private final Queue<Task> handleEvent;
DummyLuaMachine( TimeoutState state, Queue<Task> handleEvent )
{
this.state = state;
this.handleEvent = handleEvent;
}
@Override
public void addAPI( @Nonnull ILuaAPI api )
{
}
@Override
public MachineResult loadBios( @Nonnull InputStream bios )
{
return MachineResult.OK;
}
@Override
public MachineResult handleEvent( @Nullable String eventName, @Nullable Object[] arguments )
{
try
{
return handleEvent.remove().run( state );
}
catch( Throwable e )
{
errorLock.lock();
try
{
if( error == null )
{
error = e;
hasError.signal();
}
else
{
error.addSuppressed( e );
}
}
finally
{
errorLock.unlock();
}
if( !(e instanceof Exception) && !(e instanceof AssertionError) ) rethrow( e );
return MachineResult.error( e.getMessage() );
}
}
@Override
public void close()
{
}
}
}

View File

@ -5,7 +5,7 @@
*/ */
package dan200.computercraft.core.terminal; package dan200.computercraft.core.terminal;
import dan200.computercraft.ContramapMatcher; import dan200.computercraft.support.ContramapMatcher;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
@ -36,11 +36,11 @@ public class TerminalMatchers
public static Matcher<Terminal> linesMatchWith( String kind, LineProvider getLine, Matcher<String>[] lines ) public static Matcher<Terminal> linesMatchWith( String kind, LineProvider getLine, Matcher<String>[] lines )
{ {
return new ContramapMatcher<>( kind, terminal -> { return ContramapMatcher.contramap( Matchers.array( lines ), kind, terminal -> {
String[] termLines = new String[terminal.getHeight()]; String[] termLines = new String[terminal.getHeight()];
for( int i = 0; i < termLines.length; i++ ) termLines[i] = getLine.getLine( terminal, i ).toString(); for( int i = 0; i < termLines.length; i++ ) termLines[i] = getLine.getLine( terminal, i ).toString();
return termLines; return termLines;
}, Matchers.array( lines ) ); } );
} }
@FunctionalInterface @FunctionalInterface

View File

@ -5,8 +5,9 @@
*/ */
package dan200.computercraft.core.terminal; package dan200.computercraft.core.terminal;
import dan200.computercraft.api.lua.LuaValues;
import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.utils.CallCounter; import dan200.computercraft.support.CallCounter;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
@ -293,7 +294,7 @@ class TerminalTest
CallCounter callCounter = new CallCounter(); CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter ); Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.blit( "test", "1234", "abcd" ); blit( terminal, "test", "1234", "abcd" );
assertThat( terminal, allOf( assertThat( terminal, allOf(
textMatches( new String[] { textMatches( new String[] {
@ -322,7 +323,7 @@ class TerminalTest
terminal.setCursorPos( 2, 1 ); terminal.setCursorPos( 2, 1 );
callCounter.reset(); callCounter.reset();
terminal.blit( "hi", "11", "ee" ); blit( terminal, "hi", "11", "ee" );
assertThat( terminal, allOf( assertThat( terminal, allOf(
textMatches( new String[] { textMatches( new String[] {
@ -354,13 +355,13 @@ class TerminalTest
terminal.setCursorPos( 2, -5 ); terminal.setCursorPos( 2, -5 );
callCounter.reset(); callCounter.reset();
terminal.blit( "hi", "11", "ee" ); blit( terminal, "hi", "11", "ee" );
assertThat( terminal, old.matches() ); assertThat( terminal, old.matches() );
callCounter.assertNotCalled(); callCounter.assertNotCalled();
terminal.setCursorPos( 2, 5 ); terminal.setCursorPos( 2, 5 );
callCounter.reset(); callCounter.reset();
terminal.blit( "hi", "11", "ee" ); blit( terminal, "hi", "11", "ee" );
assertThat( terminal, old.matches() ); assertThat( terminal, old.matches() );
callCounter.assertNotCalled(); callCounter.assertNotCalled();
} }
@ -584,7 +585,7 @@ class TerminalTest
{ {
Terminal writeTerminal = new Terminal( 2, 1 ); Terminal writeTerminal = new Terminal( 2, 1 );
writeTerminal.blit( "hi", "11", "ee" ); blit( writeTerminal, "hi", "11", "ee" );
writeTerminal.setCursorPos( 2, 5 ); writeTerminal.setCursorPos( 2, 5 );
writeTerminal.setTextColour( 3 ); writeTerminal.setTextColour( 3 );
writeTerminal.setBackgroundColour( 5 ); writeTerminal.setBackgroundColour( 5 );
@ -614,7 +615,7 @@ class TerminalTest
void testNbtRoundtrip() void testNbtRoundtrip()
{ {
Terminal writeTerminal = new Terminal( 10, 5 ); Terminal writeTerminal = new Terminal( 10, 5 );
writeTerminal.blit( "hi", "11", "ee" ); blit( writeTerminal, "hi", "11", "ee" );
writeTerminal.setCursorPos( 2, 5 ); writeTerminal.setCursorPos( 2, 5 );
writeTerminal.setTextColour( 3 ); writeTerminal.setTextColour( 3 );
writeTerminal.setBackgroundColour( 5 ); writeTerminal.setBackgroundColour( 5 );
@ -687,6 +688,11 @@ class TerminalTest
assertEquals( 5, Terminal.getColour( 'Z', Colour.LIME ) ); assertEquals( 5, Terminal.getColour( 'Z', Colour.LIME ) );
} }
private static void blit( Terminal terminal, String text, String fg, String bg )
{
terminal.blit( LuaValues.encode( text ), LuaValues.encode( fg ), LuaValues.encode( bg ) );
}
private static final class TerminalBufferSnapshot private static final class TerminalBufferSnapshot
{ {
final String[] textLines; final String[] textLines;

View File

@ -3,7 +3,7 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com * Send enquiries to dratcliffe@gmail.com
*/ */
package dan200.computercraft.utils; package dan200.computercraft.support;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;

View File

@ -0,0 +1,56 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.support;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BooleanSupplier;
/**
* Utilities for working with concurrent systems.
*/
public class ConcurrentHelpers
{
private static final long DELAY = TimeUnit.MILLISECONDS.toNanos( 2 );
/**
* Wait until a condition is true, checking the condition every 2ms.
*
* @param isTrue The condition to check
* @return How long we waited for.
*/
public static long waitUntil( BooleanSupplier isTrue )
{
long start = System.nanoTime();
while( true )
{
if( isTrue.getAsBoolean() ) return System.nanoTime() - start;
LockSupport.parkNanos( DELAY );
}
}
/**
* Wait until a condition is true or a timeout is elapsed, checking the condition every 2ms.
*
* @param isTrue The condition to check
* @param timeout The delay after which we will timeout.
* @param unit The time unit the duration is measured in.
* @return {@literal true} if the condition was met, {@literal false} if we timed out instead.
*/
public static boolean waitUntil( BooleanSupplier isTrue, long timeout, TimeUnit unit )
{
long start = System.nanoTime();
long timeoutNs = unit.toNanos( timeout );
while( true )
{
long time = System.nanoTime() - start;
if( isTrue.getAsBoolean() ) return true;
if( time > timeoutNs ) return false;
LockSupport.parkNanos( DELAY );
}
}
}

View File

@ -3,42 +3,34 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com * Send enquiries to dratcliffe@gmail.com
*/ */
package dan200.computercraft; package dan200.computercraft.support;
import org.hamcrest.Description; import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import java.util.function.Function; import java.util.function.Function;
public class ContramapMatcher<T, U> extends TypeSafeDiagnosingMatcher<T> /**
* Given some function from {@code T} to {@code U}, converts a {@code Matcher<U>} to {@code Matcher<T>}. This is useful
* when you want to match on a particular field (or some other projection) as part of a larger matcher.
*
* @param <T> The type of the object to be matched.
* @param <U> The type of the projection/field to be matched.
*/
public final class ContramapMatcher<T, U> extends FeatureMatcher<T, U>
{ {
private final String desc;
private final Function<T, U> convert; private final Function<T, U> convert;
private final Matcher<U> matcher;
public ContramapMatcher( String desc, Function<T, U> convert, Matcher<U> matcher ) public ContramapMatcher( String desc, Function<T, U> convert, Matcher<U> matcher )
{ {
this.desc = desc; super( matcher, desc, desc );
this.convert = convert; this.convert = convert;
this.matcher = matcher;
} }
@Override @Override
protected boolean matchesSafely( T item, Description mismatchDescription ) protected U featureValueOf( T actual )
{ {
U converted = convert.apply( item ); return convert.apply( actual );
if( matcher.matches( converted ) ) return true;
mismatchDescription.appendText( desc ).appendText( " " );
matcher.describeMismatch( converted, mismatchDescription );
return false;
}
@Override
public void describeTo( Description description )
{
description.appendText( desc ).appendText( " " ).appendDescriptionOf( matcher );
} }
public static <T, U> Matcher<T> contramap( Matcher<U> matcher, String desc, Function<T, U> convert ) public static <T, U> Matcher<T> contramap( Matcher<U> matcher, String desc, Function<T, U> convert )

View File

@ -0,0 +1,110 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.support;
import com.google.common.io.ByteStreams;
import net.minecraftforge.fml.unsafe.UnsafeHacks;
import org.junit.jupiter.api.extension.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.CodeSource;
import java.security.SecureClassLoader;
/**
* Runs a test method in an entirely isolated {@link ClassLoader}, so you can mess around with as much of
* {@link dan200.computercraft} as you like.
*
* This <strong>IS NOT</strong> a good idea, but helps us run some tests in parallel while having lots of (terrible)
* global state.
*/
public class IsolatedRunner implements InvocationInterceptor, BeforeEachCallback, AfterEachCallback
{
private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create( new Object() );
@Override
public void beforeEach( ExtensionContext context ) throws Exception
{
ClassLoader loader = context.getStore( NAMESPACE ).getOrComputeIfAbsent( IsolatedClassLoader.class );
// Rename the global thread group to something more obvious.
ThreadGroup group = (ThreadGroup) loader.loadClass( "dan200.computercraft.shared.util.ThreadUtils" ).getMethod( "group" ).invoke( null );
Field field = ThreadGroup.class.getDeclaredField( "name" );
UnsafeHacks.setField( field, group, "<" + context.getDisplayName() + ">" );
}
@Override
public void afterEach( ExtensionContext context ) throws Exception
{
ClassLoader loader = context.getStore( NAMESPACE ).get( IsolatedClassLoader.class, IsolatedClassLoader.class );
loader.loadClass( "dan200.computercraft.core.computer.ComputerThread" )
.getDeclaredMethod( "stop" )
.invoke( null );
}
@Override
public void interceptTestMethod( Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext ) throws Throwable
{
invocation.skip();
ClassLoader loader = extensionContext.getStore( NAMESPACE ).get( IsolatedClassLoader.class, IsolatedClassLoader.class );
Method method = invocationContext.getExecutable();
Class<?> ourClass = loader.loadClass( method.getDeclaringClass().getName() );
Method ourMethod = ourClass.getDeclaredMethod( method.getName(), method.getParameterTypes() );
try
{
ourMethod.invoke( ourClass.getConstructor().newInstance(), invocationContext.getArguments().toArray() );
}
catch( InvocationTargetException e )
{
throw e.getTargetException();
}
}
private static class IsolatedClassLoader extends SecureClassLoader
{
IsolatedClassLoader()
{
super( IsolatedClassLoader.class.getClassLoader() );
}
@Override
public Class<?> loadClass( String name, boolean resolve ) throws ClassNotFoundException
{
synchronized( getClassLoadingLock( name ) )
{
Class<?> c = findLoadedClass( name );
if( c != null ) return c;
if( name.startsWith( "dan200.computercraft." ) )
{
CodeSource parentSource = getParent().loadClass( name ).getProtectionDomain().getCodeSource();
byte[] contents;
try( InputStream stream = getResourceAsStream( name.replace( '.', '/' ) + ".class" ) )
{
if( stream == null ) throw new ClassNotFoundException( name );
contents = ByteStreams.toByteArray( stream );
}
catch( IOException e )
{
throw new ClassNotFoundException( name, e );
}
return defineClass( name, contents, 0, contents.length, parentSource );
}
}
return super.loadClass( name, resolve );
}
}
}

View File

@ -0,0 +1,2 @@
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.dynamic.factor=4

View File

@ -177,8 +177,15 @@ describe("The textutils library", function()
expect(textutils.unserializeJSON("null", { parse_null = false })):eq(nil) expect(textutils.unserializeJSON("null", { parse_null = false })):eq(nil)
end) end)
it("an empty array", function() it("an empty array when parse_empty_array is true", function()
expect(textutils.unserializeJSON("[]", { parse_null = false })):eq(textutils.empty_json_array) expect(textutils.unserializeJSON("[]")):eq(textutils.empty_json_array)
expect(textutils.unserializeJSON("[]", { parse_empty_array = true })):eq(textutils.empty_json_array)
end)
it("an empty array when parse_empty_array is false", function()
expect(textutils.unserializeJSON("[]", { parse_empty_array = false }))
:ne(textutils.empty_json_array)
:same({})
end) end)
it("basic objects", function() it("basic objects", function()

View File

@ -9,6 +9,8 @@ import exampleNft from "./mount/example.nft";
import exampleAudioLicense from "./mount/example.dfpwm.LICENSE"; import exampleAudioLicense from "./mount/example.dfpwm.LICENSE";
import exampleAudioUrl from "./mount/example.dfpwm"; import exampleAudioUrl from "./mount/example.dfpwm";
import "./prism.js";
const defaultFiles: { [filename: string]: string } = { const defaultFiles: { [filename: string]: string } = {
".settings": settingsFile, ".settings": settingsFile,
"startup.lua": startupFile, "startup.lua": startupFile,
@ -76,7 +78,9 @@ class Window extends Component<WindowProps, WindowState> {
const snippet = element.getAttribute("data-snippet"); const snippet = element.getAttribute("data-snippet");
if (snippet) this.snippets[snippet] = example; if (snippet) this.snippets[snippet] = example;
if (element.getAttribute("data-lua-kind") == "expr") { // We attempt to pretty-print the result of a function _except_ when the function
// is print. This is pretty ugly, but prevents the confusing trailing "1".
if (element.getAttribute("data-lua-kind") == "expr" && !example.startsWith("print(")) {
example = exprTemplate.replace("__expr__", example); example = exprTemplate.replace("__expr__", example);
} }
@ -98,7 +102,7 @@ class Window extends Component<WindowProps, WindowState> {
</div> </div>
<div class="computer-container"> <div class="computer-container">
<Computer key={exampleIdx} files={{ <Computer key={exampleIdx} files={{
...example!.files, ...defaultFiles ...defaultFiles, ...example!.files,
}} peripherals={{ back: example!.peripheral }} /> }} peripherals={{ back: example!.peripheral }} />
</div> </div>
</div> : <div class="example-window example-window-hidden" />; </div> : <div class="example-window example-window-hidden" />;

5
src/web/prism.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -7,21 +7,21 @@
} }
/* Pretty tables, mostly inherited from table.definition-list */ /* Pretty tables, mostly inherited from table.definition-list */
table.pretty-table { table {
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
} }
table.pretty-table td, table.pretty-table th { table td, table th {
border: 1px solid #cccccc; border: 1px solid #cccccc;
padding: 2px 4px; padding: 2px 4px;
} }
table.pretty-table th { table th {
background-color: var(--background-2); background-color: var(--background-2);
} }
pre.highlight.highlight-lua { pre.highlight {
position: relative; position: relative;
} }