1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-16 14:37:39 +00:00

Compare commits

..

39 Commits

Author SHA1 Message Date
Jonathan Coates
1da86d6f75 Don't run CI on 1.12 2022-05-07 16:56:00 +01:00
Jonathan Coates
53a23f8d3d Use a simpler getRenderBoundingBox method for cables
Our cable has a rather complex getCollisionBoundingBox implementation,
which (combined with computing the actual block state) ends up taking an
awful lot of time.

This shaves 9% off each frame. I don't miss 1.12 one bit.
2022-05-07 16:50:48 +01:00
Jonathan Coates
550f63b1cb Rethink how computer timeouts are handled
Previously we would compute the current timeout flags every 128
instructions of the Lua machine. While computing these flags is
_relatively_ cheap (just get the current time), it still all adds up.

Instead we now set the timeout flags from the computer monitor/watchdog
thread. This does mean that the monitor thread needs to wake up more
often[^1] _if the queue is full_, otherwise we can sleep for 100ms as
before.

This does mean that pausing is a little less accurate (can technically
take up 2*period instead). This isn't great, but in practice it seems
fine - I've not noticed any playability issues.

This offers a small (but measurable!) boost to computer performance.

[^1]: We currently sleep for scaledPeriod, but we could choose to do less.
2022-05-07 16:50:40 +01:00
Jonathan Coates
416e87852e Bump Cobalt version 2022-05-07 16:50:07 +01:00
Jonathan Coates
7940687df2 Get this building in the year of our lord 2022
- Force the license check to use 2020
 - Tell Proguard to ignore some annotations
2022-05-07 16:49:07 +01:00
SquidDev
37a447e745 Bump version to 1.89.2
Somewhat reluctant to do this, but it's a pretty major bug.
2020-06-30 11:10:26 +01:00
SquidDev
9e2232d240 Clean up entity drop code
We were incorrectly using captureDrops directly - it's more reasonable
to listen to the drop event. Fixes #486
2020-06-30 11:10:24 +01:00
SquidDev
2a8efb3fd5 Fix crashes when rendering monitors of varying sizes
When calling .flip(), we limit the size of the buffer. However, this
limit is not reset when writing the next time, which means we get
out-of-bounds errors, even if the buffer is /technically/ big enough.

Clearing the buffer before drawing (rather than just resetting the
position) is enough to fix this.

Fixes #476 (and closes #477, which is a duplicate)
2020-06-21 12:03:24 +01:00
SquidDev
c0f3ca81fb Bump to 1.89.0 2020-06-15 21:37:08 +01:00
Lignum
014bf55cd4 Cherry pick several improvements from #455
- Use texture over texture2D - the latter was deprecated in GLSL 1.30.
 - Cache the tbo buffer - this saves an allocation when monitors update.

Closes #455. While the rest of the PR has some nice changes, it
performs signlificantly worse on my system.
2020-05-31 17:23:49 +01:00
Lignum
085ae2e74a Use an older version of GLSL (#459)
This ensures that the MVP matrix is available within the monitor
fragment shader, without requiring the ARB_compatibility extension.
2020-05-28 11:06:14 +01:00
Lignum
4ff33f165d Fetch MVP matrix in monitor shader instead (#454) 2020-05-25 11:19:03 +01:00
SquidDev
d929c02d2a Fix settings loading failing for defined settings
Yes, this was the only piece of code which wasn't tested :/. Fixes #457.
2020-05-24 12:16:51 +01:00
Jonathan Coates
d50a08a549 Rewrite monitor networking (#453)
This moves monitor networking into its own packet, rather than serialising
using NBT. This allows us to be more flexible with how monitors are
serialised.

We now compress terminal data using gzip. This reduces the packet size
of a max-sized-monitor from ~25kb to as little as 100b.

On my test set of images (what I would consider to be the extreme end of
the "reasonable" case), we have packets from 1.4kb bytes up to 12kb,
with a mean of 6kb. Even in the worst case, this is a 2x reduction in
packet size.

While this is a fantastic win for the common case, it is not abuse-proof.
One can create a terminal with high entropy (and so uncompressible). This
will still be close to the original packet size.

In order to prevent any other abuse, we also limit the amount of monitor
data a client can possibly receive to 1MB (configurable).
2020-05-20 08:44:44 +01:00
SquidDev
161a5b4707 Document and test the redstone library
The tests may be a little agressive, but I wanted some sanity checks for
the 1.15 API rewrite.
2020-05-15 10:03:47 +01:00
SquidDev
c6b6b4479c Update changelog and fix doc typo 2020-05-14 19:23:57 +01:00
SquidDev
96e7b60285 Display function arguments and positions in the REPL
- cc.pretty.pretty now accepts two additional options:
   - function_args: Show function arguments
   - function_source: Show where functions are defined.
 - Expose the two options as lua.* settings (defaulting function_args to
   true, and function_source to false).
   These are then used in the Lua REPL.

Closes #361
2020-05-14 19:11:57 +01:00
SquidDev
086fccd997 Move the package library into a separate module
Hopefully this makes it a little easier for people to use in custom
shells (and anything else where it might be useful).
2020-05-14 17:27:50 +01:00
SquidDev
a4ae36b6b3 Bump version to 1.88.0
There's probably some other stuff I'll get in before release, but let's
do this just in case.
2020-05-13 13:43:40 +01:00
SquidDev
ac075d9f53 Allow using command computers in survival mode
I'm really not a fan of this change, but it's gated behind a config
option and there's apparently sufficient demand that it's worthwhile.
Closes #442.
2020-05-13 10:26:59 +01:00
Lupus590
05d7be0362 Improvements to the various file system programs (rm, mv, rename) (#440)
This enforces several sanity checks before actually attempting
the move, allowing us to produce friendlier error messages.
2020-05-12 11:32:48 +01:00
SquidDev
9a71dc1a26 Copy across a bunch of 5.1/5.3 io tests
I've been meaning to do this for ages. Woops.
2020-05-11 18:05:40 +01:00
SquidDev
156023b154 Create more work for myself
This ensures no lines start with an empty line, and all files finish
with exactly one "\n".
2020-05-11 16:08:25 +01:00
SquidDev
6b3773a862 Run tests with coverage
- Use jacoco for Java-side coverage. Our Java coverage is /terrible
   (~10%), as we only really test the core libraries. Still a good thing
   to track for regressions though.

 - mcfly now tracks Lua side coverage. This works in several stages:
   - Replace loadfile to include the whole path
   - Add a debug hook which just tracks filename->(lines->count). This
     is then submitted to the Java test runner.
   - On test completion, we emit a luacov.report.out file.

   As the debug hook is inserted by mcfly, this does not include any
   computer startup (such as loading apis, or the root of bios.lua),
   despite they're executed.

   This would be possible to do (for instance, inject a custom header
   into bios.lua). However, we're not actually testing any of the
   behaviour of startup (aside from "does it not crash"), so I'm not
   sure whether to include it or not. Something I'll most likely
   re-evaluate.
2020-05-11 15:47:30 +01:00
Jonathan Coates
376d628cf0 Make the local Lua message a little shorter
Co-authored-by: exerro <benedict.allen2514@gmail.com>
2020-05-09 08:30:55 +01:00
Lupus590
44062ebd52 Allow lua REPL to warn about using local variables (#367)
`local varname = value` results in `varname` being inaccessible in
the next REPL input. This is often unintended and can lead to confusing
behaviour. We produce a warning when this occurs.
2020-05-08 16:07:33 +01:00
Jonathan Coates
5739285fc2 Finish off documentation for the commands API 2020-05-05 21:17:52 +01:00
Jonathan Coates
70b457ed18 Add a monitor renderer using TBOs (#443)
This uses the system described in #409, to render monitors in a more
efficient manner.

Each monitor is backed by a texture buffer object (TBO) which contains
a relatively compact encoding of the terminal state. This is then
rendered using a shader, which consumes the TBO and uses it to index
into main font texture.

As we're transmitting significantly less data to the GPU (only 3 bytes
per character), this effectively reduces any update lag to 0. FPS appears
to be up by a small fraction (10-15fps on my machine, to ~110), possibly
as we're now only drawing a single quad (though doing much more work in
the shader).

On my laptop, with its Intel integrated graphics card, I'm able to draw
120 full-sized monitors (with an effective resolution of 3972 x 2330) at
a consistent 60fps. Updates still cause a slight spike, but we always
remain above 30fps - a significant improvement over VBOs, where updates
would go off the chart.

Many thanks to @Lignum and @Lemmmy for devising this scheme, and helping
test and review it! ♥
2020-05-05 13:05:23 +01:00
SquidDev
1547ecbeb3 Fix incorrect palette serialisation 2020-05-04 10:26:33 +01:00
SquidDev
550ada2f9e Restore previous behaviour for unknown colours
Unknown blit colours, such as " " will be translated to black for the
background or white for the foreground. This restores the behaviour from
before #412.
2020-05-04 09:15:23 +01:00
SquidDev
17b7727262 Improve serialisation of terminals
- Write to a PacketBuffer instead of generating an NBT tag. This is
   then converted to an NBT byte array when we send across the network.
 - Pack background/foreground colours into a single byte.

This derives from some work I did back in 2017, and some of the changes
made/planned in #409. However, this patch does not change how terminals
are represented, it simply makes the transfer more compact.

This makes the patch incredibly small (100 lines!), but also limited in
what improvements it can make compared with #409. We send 26626 bytes
for a full-sized monitor. While a 2x improvement over the previous 58558
bytes, there's a lot of room for improvement.
2020-05-03 10:38:31 +01:00
Jonathan Coates
4553e404b2 Merge pull request #437 from SquidDev-CC/feature/keep-ids
Preserve computer ids on unlabelled computers
2020-05-03 06:54:04 +01:00
Drew Lemmy
a565a571f9 Return the peripheral name when wrapping (#436) 2020-05-03 06:53:42 +01:00
SquidDev
fb64b6017b Add shell.execute
This functions the same as shell.run, but does not tokenise the
arguments. This allows us to pass command line arguments through to
another program without having to re-quote them.

Closes #417
2020-05-02 11:05:09 +01:00
SquidDev
ed4229ab70 Keep ids of unlabelled computers and turtles 2020-05-02 10:38:18 +01:00
SquidDev
3fb906ef6c Show computer/disk ids in the tool tip more often
- Remove the parenthesis around the text (so it's now
   "Computer ID: 12"), rather than "(Computer ID: 12").
 - Show the tooltip if the computer has an ID and no label (as well as
   when in advanced mode).
2020-05-02 10:38:17 +01:00
Jonathan Coates
e1663f3df0 Fix malformed doc comments 2020-05-01 08:50:44 +01:00
Jonathan Coates
447c3ab125 Clean up dance.lua
Not sure what keys.escape was doing there. That's very old.
2020-04-28 09:51:06 +01:00
Jonathan Coates
8fac68386e Fix usages of global variables
- Lint references to unknown fields of modules, excluding the keys and
   colours modules. This caught several silly errors in our stub files,
   but nothing else.
 - Lint on using unknown globals. This highlighted a couple of really
   silly mistakes. Fixes #427.
 - Add documentation for fs.attributes, fs.getCapacity and pocket, as
   they were not defined before.

Co-authored-by: JackMacWindows <jackmacwindowslinux@gmail.com>
2020-04-28 09:42:34 +01:00
127 changed files with 2759 additions and 668 deletions

View File

@@ -1,50 +0,0 @@
name: Build
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Cache gradle dependencies
uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('gradle.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build with Gradle
run: ./gradlew build --no-daemon
- name: Upload Jar
uses: actions/upload-artifact@v1
with:
name: CC-Tweaked
path: build/libs
lint-lua:
name: Lint Lua
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Lint Lua code
run: |
test -d bin || mkdir bin
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
chmod +x bin/illuaminate
bin/illuaminate lint
- name: Check whitespace
run: python3 tools/check-lines.py

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env bash
set -eu
DEST="${GITHUB_REF#refs/*/}"
echo "Uploading docs to https://tweaked.cc/$DEST"
# Setup ssh key
mkdir -p "$HOME/.ssh/"
echo "$SSH_KEY" > "$HOME/.ssh/key"
chmod 600 "$HOME/.ssh/key"
# And upload
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
"$GITHUB_WORKSPACE/doc/" \
"$SSH_USER@$SSH_HOST:/var/www/tweaked.cc/$DEST"

View File

@@ -1,29 +0,0 @@
name: Build documentation
on:
push:
branches: [ master ]
tags:
jobs:
make_doc:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build documentation
run: |
test -d bin || mkdir bin
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
chmod +x bin/illuaminate
bin/illuaminate doc-gen
- name: Upload documentation
run: .github/workflows/make-doc.sh 2> /dev/null
env:
SSH_KEY: ${{ secrets.SSH_KEY }}
SSH_USER: ${{ secrets.SSH_USER }}
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_PORT: ${{ secrets.SSH_PORT }}

View File

@@ -18,6 +18,7 @@ buildscript {
plugins {
id "checkstyle"
id "jacoco"
id "com.github.hierynomus.license" version "0.15.0"
id "com.matthewprenger.cursegradle" version "1.3.0"
id "com.github.breadmoirai.github-release" version "2.2.4"
@@ -51,14 +52,6 @@ repositories {
name "Charset"
artifactPattern "https://asie.pl/files/mods/Charset/LibOnly/[module]-[revision](-[classifier]).[ext]"
}
maven {
name "Amadornes"
url "https://maven.amadornes.com/"
}
maven {
name "CraftTweaker"
url "https://maven.blamejared.com/"
}
}
configurations {
@@ -77,7 +70,7 @@ dependencies {
runtime "mezz.jei:jei_1.12.2:4.15.0.269"
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
shade 'org.squiddev:Cobalt:0.5.5'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
@@ -143,6 +136,9 @@ task proguard(type: ProGuardTask, dependsOn: jar) {
// We want to avoid as much obfuscation as possible. We're only doing this to shrink code size.
dontobfuscate; dontoptimize; keepattributes; keepparameternames
// Tell ProGuard to shut up
dontwarn 'org.checkerframework.**'
// Proguard will remove directories by default, but that breaks JarMount.
keepdirectories 'assets/computercraft/lua**'
@@ -251,11 +247,20 @@ test {
}
}
jacocoTestReport {
reports {
xml.enabled true
html.enabled true
}
}
check.dependsOn jacocoTestReport
license {
mapping("java", "SLASHSTAR_STYLE")
strictCheck true
ext.year = Calendar.getInstance().get(Calendar.YEAR)
ext.year = 2020
}
[licenseMain, licenseFormatMain].forEach {

View File

@@ -1,6 +1,77 @@
--- Execute a specific command.
--
-- @tparam string command The command to execute.
-- @treturn boolean Whether the command executed successfully.
-- @treturn { string... } The output of this command, as a list of lines.
-- @treturn number|nil The number of "affected" objects, or `nil` if the command
-- failed. The definition of this varies from command to command.
-- @usage Set the block above the command computer to stone.
--
-- commands.exec("setblock ~ ~1 ~ minecraft:stone")
function exec(command) end
--- Asynchronously execute a command.
--
-- Unlike @{exec}, this will immediately return, instead of waiting for the
-- command to execute. This allows you to run multiple commands at the same
-- time.
--
-- When this command has finished executing, it will queue a `task_complete`
-- event containing the result of executing this command (what @{exec} would
-- return).
--
-- @tparam string command The command to execute.
-- @treturn number The "task id". When this command has been executed, it will
-- queue a `task_complete` event with a matching id.
-- @usage Asynchronously sets the block above the computer to stone.
--
-- commands.execAsync("~ ~1 ~ minecraft:stone")
-- @see parallel One may also use the parallel API to run multiple commands at
-- once.
function execAsync(commad) end
--- List all available commands which the computer has permission to execute.
--
-- @treturn { string... } A list of all available commands
function list() end
--- Get the position of the current command computer.
--
-- @treturn number This computer's x position.
-- @treturn number This computer's y position.
-- @treturn number This computer's z position.
-- @see gps.locate To get the position of a non-command computer.
function getBlockPosition() end
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end
--- Get some basic information about a block.
--
-- The returned table contains the current name, metadata and block state (as
-- with @{turtle.inspect}). If there is a tile entity for that block, its NBT
-- will also be returned.
--
-- @tparam number x The x position of the block to query.
-- @tparam number y The y position of the block to query.
-- @tparam number z The z position of the block to query.
-- @treturn table The given block's information.
-- @throws If the coordinates are not within the world, or are not currently
-- loaded.
function getBlockInfo(x, y, z) end
--- Get information about a range of blocks.
--
-- This returns the same information as @{getBlockInfo}, just for multiple
-- blocks at once.
--
-- Blocks are traversed by ascending y level, followed by z and x - the returned
-- table may be indexed using `x + z*width + y*depth*depth`.
--
-- @tparam number min_x The start x coordinate of the range to query.
-- @tparam number min_y The start y coordinate of the range to query.
-- @tparam number min_z The start z coordinate of the range to query.
-- @tparam number max_x The end x coordinate of the range to query.
-- @tparam number max_y The end y coordinate of the range to query.
-- @tparam number max_z The end z coordinate of the range to query.
-- @treturn { table... } A list of information about each block.
-- @throws If the coordinates are not within the world.
-- @throws If trying to get information about more than 4096 blocks.
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end

View File

@@ -19,6 +19,48 @@ function getFreeSpace(path) end
function find(pattern) end
function getDir(path) end
--- Returns true if a path is mounted to the parent filesystem.
--
-- The root filesystem "/" is considered a mount, along with disk folders and
-- the rom folder. Other programs (such as network shares) can exstend this to
-- make other mount types by correctly assigning their return value for getDrive.
--
-- @tparam string path The path to check.
-- @treturn boolean If the path is mounted, rather than a normal file/folder.
-- @throws If the path does not exist.
-- @see getDrive
function isDriveRoot(path) end
--- Get the capacity of the drive at the given path.
--
-- This may be used in conjunction with @{getFreeSpace} to determine what
-- percentage of this drive has been used.
--
-- @tparam string path The path of the drive to get.
-- @treturn number This drive's capacity. This will be 0 for "read-only" drives,
-- such as the ROM or treasure disks.
function getCapacity(path) end
--- Get attributes about a specific file or folder.
--
-- The returned attributes table contains information about the size of the
-- file, whether it is a directory, and when it was created and last modified.
--
-- The creation and modification times are given as the number of milliseconds
-- since the UNIX epoch. This may be given to @{os.date} in order to convert it
-- to more usable form.
--
-- @tparam string path The path to get attributes for.
-- @treturn { size = number, isDir = boolean, created = number, modified = number }
-- The resulting attributes.
-- @throws If the path does not exist.
-- @see getSize If you only care about the file's size.
-- @see isDir If you only care whether a path is a directory or not.
function attributes(path) end
-- Defined in bios.lua
function complete(sPath, sLocation, bIncludeFiles, bIncludeDirs) end
--- A file handle which can be read from.
--
-- @type ReadHandle

View File

@@ -15,3 +15,10 @@ function cancelTimer(id) end
function cancelAlarm(id) end
function epoch(timezone) end
function date(format, time) end
-- Defined in bios.lua
function loadAPI(path) end
function pullEvent(filter) end
function pullEventRaw(filter) end
function version() end
function run(env, path, ...) end

28
doc/stub/pocket.lua Normal file
View File

@@ -0,0 +1,28 @@
--[[-
Control the current pocket computer, adding or removing upgrades.
This API is only available on pocket computers. As such, you may use its
presence to determine what kind of computer you are using:
```lua
if pocket then
print("On a pocket computer")
else
print("On something else")
end
```
]]
--- Search the player's inventory for another upgrade, replacing the existing
-- one with that item if found.
--
-- This inventory search starts from the player's currently selected slot,
-- allowing you to prioritise upgrades.
--
-- @throws If an upgrade cannot be found.
function equipBack() end
--- Remove the pocket computer's current upgrade.
--
-- @throws If this pocket computer does not currently have an upgrade.
function unequipBack() end

View File

@@ -1,14 +1,120 @@
--[[- Interact with redstone attached to this computer.
The @{redstone} library exposes three "types" of redstone control:
- Binary input/output (@{setOutput}/@{getInput}): These simply check if a
redstone wire has any input or output. A signal strength of 1 and 15 are
treated the same.
- Analogue input/output (@{setAnalogueOutput}/@{getAnalogueInput}): These
work with the actual signal strength of the redstone wired, from 0 to 15.
- Bundled cables (@{setBundledOutput}/@{getBundledInput}): These interact with
"bundled" cables, such as those from Project:Red. These allow you to send
16 separate on/off signals. Each channel corresponds to a colour, with the
first being @{colors.white} and the last @{colors.black}.
Whenever a redstone input changes, a `redstone` event will be fired. This may
be used in or
This module may also be referred to as `rs`. For example, one may call
`rs.getSides()` instead of @{redstone.getSides}.
@module redstone
@usage Toggle the redstone signal above the computer every 0.5 seconds.
while true do
redstone.setOutput("top", not redstone.getOutput("top"))
sleep(0.5)
end
@usage Mimic a redstone comparator in [subtraction mode][comparator].
while true do
local rear = rs.getAnalogueInput("back")
local sides = math.max(rs.getAnalogueInput("left"), rs.getAnalogueInput("right"))
rs.setAnalogueOutput("front", math.max(rear - sides, 0))
os.pullEvent("redstone") -- Wait for a change to inputs.
end
[comparator]: https://minecraft.gamepedia.com/Redstone_Comparator#Subtract_signal_strength "Redstone Comparator on the Minecraft wiki."
]]
--- Returns a table containing the six sides of the computer. Namely, "top",
-- "bottom", "left", "right", "front" and "back".
--
-- @treturn { string... } A table of valid sides.
function getSides() end
--- Turn the redstone signal of a specific side on or off.
--
-- @tparam string side The side to set.
-- @tparam boolean on Whether the redstone signal should be on or off. When on,
-- a signal strength of 15 is emitted.
function setOutput(side, on) end
--- Get the current redstone output of a specific side.
--
-- @tparam string side The side to get.
-- @treturn boolean Whether the redstone output is on or off.
-- @see setOutput
function getOutput(side) end
--- Get the current redstone input of a specific side.
--
-- @tparam string side The side to get.
-- @treturn boolean Whether the redstone input is on or off.
function getInput(side) end
function setBundledOutput(side, output) end
function getBundledOutput(side) end
function getBundledInput(side) end
function testBundledInput(side, mask) end
--- Set the redstone signal strength for a specific side.
--
-- @tparam string side The side to set.
-- @tparam number value The signal strength, between 0 and 15.
-- @throws If `value` is not between 0 and 15.
function setAnalogOutput(side, value) end
setAnalogueOutput = setAnalogOutput
--- Get the redstone output signal strength for a specific side.
--
-- @tparam string side The side to get.
-- @treturn number The output signal strength, between 0 and 15.
-- @see setAnalogueOutput
function getAnalogOutput(sid) end
getAnalogueOutput = getAnalogOutput
--- Get the redstone input signal strength for a specific side.
--
-- @tparam string side The side to get.
-- @treturn number The input signal strength, between 0 and 15.
function getAnalogInput(side) end
getAnalogueInput = getAnaloguInput
getAnalogueInput = getAnalogInput
--- Set the bundled cable output for a specific side.
--
-- @tparam string side The side to set.
-- @tparam number The colour bitmask to set.
-- @see colors.subtract For removing a colour from the bitmask.
-- @see colors.combine For adding a colour to the bitmask.
function setBundledOutput(side, output) end
--- Get the bundled cable output for a specific side.
--
-- @tparam string side The side to get.
-- @treturn number The bundled cable's output.
function getBundledOutput(side) end
--- Get the bundled cable input for a specific side.
--
-- @tparam string side The side to get.
-- @treturn number The bundled cable's input.
-- @see testBundledInput To determine if a specific colour is set.
function getBundledInput(side) end
--- Determine if a specific combination of colours are on for the given side.
--
-- @tparam string side The side to test.
-- @tparam number mask The mask to test.
-- @see getBundledInput
-- @see colors.combine For adding a colour to the bitmask.
-- @usage Check if @{colors.white} and @{colors.black} are on for above the
-- computer.
--
-- print(redstone.testBundledInput("top", colors.combine(colors.white, colors.black)))
function testBundledInput(side, mask) end

View File

@@ -15,14 +15,14 @@ isColor = isColour
function getTextColour() end
getTextColor = getTextColor
function getBackgroundColour() end
getBackgroundColour = getBackgroundColour
getBackgroundColor = getBackgroundColour
function blit(text, text_colours, background_colours) end
function setPaletteColour(colour, ...) end
setPaletteColour = setPaletteColour
setPaletteColor = setPaletteColour
function getPaletteColour(colour, ...) end
getPaletteColour = getPaletteColour
getPaletteColor = getPaletteColour
function nativePaletteColour(colour) end
nativePaletteColour = nativePaletteColour
nativePaletteColor = nativePaletteColour
--- @type Redirect
local Redirect = {}

View File

@@ -1,5 +1,5 @@
# Mod properties
mod_version=1.87.0
mod_version=1.89.2
# Minecraft properties
mc_version=1.12.2

View File

@@ -33,17 +33,27 @@
;; It's useful to name arguments for documentation, so we allow this. It'd
;; be good to find a compromise in the future, but this works for now.
-var:unused-arg
-var:unused-arg)
;; Some APIS (keys, colour and os mainly) are incomplete right now.
-var:unresolved-member)
(lint
(bracket-spaces
(call no-space)
(function-args no-space)
(parens no-space)
(table space)
(index no-space))))
(index no-space))
;; colours imports from colors, and we don't handle that right now.
;; keys is entirely dynamic, so we skip it.
(dynamic-modules colours keys)
(globals
:max
_CC_DEFAULT_SETTINGS
_CC_DISABLE_LUA51_FEATURES
;; Ideally we'd pick these up from bios.lua, but illuaminate currently
;; isn't smart enough.
sleep write printError read rs)))
;; We disable the unused global linter in bios.lua and the APIs. In the future
;; hopefully we'll get illuaminate to handle this.
@@ -60,17 +70,13 @@
;; Suppress warnings for currently undocumented modules.
(at
(/doc/stub/commands.lua
/doc/stub/fs.lua
(/doc/stub/fs.lua
/doc/stub/http.lua
/doc/stub/os.lua
/doc/stub/redstone.lua
/doc/stub/term.lua
/doc/stub/turtle.lua
/src/main/resources/*/computercraft/lua/rom/apis/command/commands.lua
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
/src/main/resources/*/computercraft/lua/rom/apis/window.lua
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua)
/src/main/resources/*/computercraft/lua/rom/apis/window.lua)
(linters -doc:undocumented -doc:undocumented-arg))
@@ -79,6 +85,11 @@
(/src/main/resources/*/computercraft/lua/rom/apis/textutils.lua
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/completion.lua
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua
/src/main/resources/*/computercraft/lua/rom/programs/advanced/multishell.lua
/src/main/resources/*/computercraft/lua/rom/programs/shell.lua)
(linters -doc:unresolved-reference))
(at /src/test/resources/test-rom
(lint
(globals
:max sleep write
cct_test describe expect howlci fail it pending stub)))

View File

@@ -112,7 +112,8 @@ public class ComputerCraft
public static boolean disable_lua51_features = false;
public static String default_computer_settings = "";
public static boolean debug_enable = true;
public static boolean logPeripheralErrors = false;
public static boolean logPeripheralErrors = true;
public static boolean commandRequireCreative = true;
public static int computer_threads = 1;
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
@@ -137,6 +138,7 @@ public class ComputerCraft
public static int modem_highAltitudeRangeDuringStorm = 384;
public static int maxNotesPerTick = 8;
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
public static long monitorBandwidth = 1_000_000;
public static boolean turtlesNeedFuel = true;
public static int turtleFuelLimit = 20000;
@@ -536,7 +538,7 @@ public class ComputerCraft
}
catch( IOException e )
{
if( zipFile != null ) IoUtil.closeQuietly( zipFile );
IoUtil.closeQuietly( zipFile );
}
}
}

View File

@@ -50,15 +50,14 @@ public final class FixedWidthFontRenderer
{
}
private static float toGreyscale( double[] rgb )
public static float toGreyscale( double[] rgb )
{
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
}
private static int getColour( char c )
public static int getColour( char c, Colour def )
{
int i = "0123456789abcdef".indexOf( c );
return i < 0 ? 0 : 15 - i;
return 15 - Terminal.getColour( c, def );
}
private static void drawChar( BufferBuilder buffer, float x, float y, int index, float r, float g, float b )
@@ -92,7 +91,7 @@ public final class FixedWidthFontRenderer
private static void drawQuad( BufferBuilder buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex )
{
double[] colour = palette.getColour( getColour( colourIndex ) );
double[] colour = palette.getColour( getColour( colourIndex, Colour.Black ) );
float r, g, b;
if( greyscale )
{
@@ -160,7 +159,7 @@ public final class FixedWidthFontRenderer
for( int i = 0; i < text.length(); i++ )
{
double[] colour = palette.getColour( getColour( textColour.charAt( i ) ) );
double[] colour = palette.getColour( getColour( textColour.charAt( i ), Colour.White ) );
float r, g, b;
if( greyscale )
{

View File

@@ -0,0 +1,158 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.google.common.base.Strings;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.renderer.OpenGlHelper;
import org.apache.commons.io.IOUtils;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
class MonitorTextureBufferShader
{
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 );
private static int uniformFont;
private static int uniformWidth;
private static int uniformHeight;
private static int uniformTbo;
private static int uniformPalette;
private static boolean initialised;
private static boolean ok;
private static int program;
static void setupUniform( int width, int height, Palette palette, boolean greyscale )
{
OpenGlHelper.glUniform1i( uniformWidth, width );
OpenGlHelper.glUniform1i( uniformHeight, height );
PALETTE_BUFFER.rewind();
for( int i = 0; i < 16; i++ )
{
double[] colour = palette.getColour( i );
if( greyscale )
{
float f = FixedWidthFontRenderer.toGreyscale( colour );
PALETTE_BUFFER.put( f ).put( f ).put( f );
}
else
{
PALETTE_BUFFER.put( (float) colour[0] ).put( (float) colour[1] ).put( (float) colour[2] );
}
}
PALETTE_BUFFER.flip();
OpenGlHelper.glUniform3( uniformPalette, PALETTE_BUFFER );
}
static boolean use()
{
if( initialised )
{
if( ok ) OpenGlHelper.glUseProgram( program );
return ok;
}
if( ok = load() )
{
GL20.glUseProgram( program );
OpenGlHelper.glUniform1i( uniformFont, 0 );
OpenGlHelper.glUniform1i( uniformTbo, TEXTURE_INDEX - GL13.GL_TEXTURE0 );
}
return ok;
}
private static boolean load()
{
initialised = true;
try
{
int vertexShader = loadShader( GL20.GL_VERTEX_SHADER, "assets/computercraft/shaders/monitor.vert" );
int fragmentShader = loadShader( GL20.GL_FRAGMENT_SHADER, "assets/computercraft/shaders/monitor.frag" );
program = OpenGlHelper.glCreateProgram();
OpenGlHelper.glAttachShader( program, vertexShader );
OpenGlHelper.glAttachShader( program, fragmentShader );
GL20.glBindAttribLocation( program, 0, "v_pos" );
OpenGlHelper.glLinkProgram( program );
boolean ok = OpenGlHelper.glGetProgrami( program, GL20.GL_LINK_STATUS ) != 0;
String log = OpenGlHelper.glGetProgramInfoLog( program, Short.MAX_VALUE ).trim();
if( !Strings.isNullOrEmpty( log ) )
{
ComputerCraft.log.warn( "Problems when linking monitor shader: {}", log );
}
GL20.glDetachShader( program, vertexShader );
GL20.glDetachShader( program, fragmentShader );
OpenGlHelper.glDeleteShader( vertexShader );
OpenGlHelper.glDeleteShader( fragmentShader );
if( !ok ) return false;
uniformFont = getUniformLocation( program, "u_font" );
uniformWidth = getUniformLocation( program, "u_width" );
uniformHeight = getUniformLocation( program, "u_height" );
uniformTbo = getUniformLocation( program, "u_tbo" );
uniformPalette = getUniformLocation( program, "u_palette" );
ComputerCraft.log.info( "Loaded monitor shader." );
return true;
}
catch( Exception e )
{
ComputerCraft.log.error( "Cannot load monitor shaders", e );
return false;
}
}
private static int loadShader( int kind, String path ) throws IOException
{
InputStream stream = TileEntityMonitorRenderer.class.getClassLoader().getResourceAsStream( path );
if( stream == null ) throw new IllegalArgumentException( "Cannot find " + path );
byte[] contents = IOUtils.toByteArray( new BufferedInputStream( stream ) );
ByteBuffer buffer = BufferUtils.createByteBuffer( contents.length );
buffer.put( contents );
buffer.position( 0 );
int shader = OpenGlHelper.glCreateShader( kind );
OpenGlHelper.glShaderSource( shader, buffer );
OpenGlHelper.glCompileShader( shader );
boolean ok = OpenGlHelper.glGetShaderi( shader, GL20.GL_COMPILE_STATUS ) != 0;
String log = OpenGlHelper.glGetShaderInfoLog( shader, Short.MAX_VALUE ).trim();
if( !Strings.isNullOrEmpty( log ) )
{
ComputerCraft.log.warn( "Problems when loading monitor shader {}: {}", path, log );
}
if( !ok ) throw new IllegalStateException( "Cannot compile shader " + path );
return shader;
}
private static int getUniformLocation( int program, String name )
{
int uniform = OpenGlHelper.glGetUniformLocation( program, name );
if( uniform == -1 ) throw new IllegalStateException( "Cannot find uniform " + name );
return uniform;
}
}

View File

@@ -8,28 +8,34 @@ package dan200.computercraft.client.render;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL31;
import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_MARGIN;
public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMonitor>
{
private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1);
private static ByteBuffer tboContents;
@Override
public void render( @Nonnull TileMonitor tileEntity, double posX, double posY, double posZ, float f, int i, float f2 )
@@ -97,8 +103,8 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( terminal != null )
{
// Draw a terminal
double xScale = xSize / (terminal.getWidth() * FixedWidthFontRenderer.FONT_WIDTH);
double yScale = ySize / (terminal.getHeight() * FixedWidthFontRenderer.FONT_HEIGHT);
double xScale = xSize / (terminal.getWidth() * FONT_WIDTH);
double yScale = ySize / (terminal.getHeight() * FONT_HEIGHT);
GlStateManager.pushMatrix();
GlStateManager.scale( (float) xScale, (float) -yScale, 1.0f );
@@ -147,6 +153,59 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
switch( renderer )
{
case TBO:
{
if( !MonitorTextureBufferShader.use() ) return;
Terminal terminal = monitor.getTerminal();
int width = terminal.getWidth(), height = terminal.getHeight();
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
if( redraw )
{
int size = width * height * 3;
if( tboContents == null || tboContents.capacity() < size )
{
tboContents = GLAllocation.createDirectByteBuffer( size );
}
ByteBuffer monitorBuffer = tboContents;
monitorBuffer.clear();
for( int y = 0; y < height; y++ )
{
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
for( int x = 0; x < width; x++ )
{
monitorBuffer.put( (byte) (text.charAt( x ) & 0xFF) );
monitorBuffer.put( (byte) getColour( textColour.charAt( x ), Colour.White ) );
monitorBuffer.put( (byte) getColour( background.charAt( x ), Colour.Black ) );
}
}
monitorBuffer.flip();
OpenGlHelper.glBindBuffer( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer );
OpenGlHelper.glBufferData( GL31.GL_TEXTURE_BUFFER, monitorBuffer, GL15.GL_STATIC_DRAW );
OpenGlHelper.glBindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
}
// Bind TBO texture and set up the uniforms. We've already set up the main font above.
GlStateManager.setActiveTexture( MonitorTextureBufferShader.TEXTURE_INDEX );
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, monitor.tboTexture );
GlStateManager.setActiveTexture( GL13.GL_TEXTURE0 );
MonitorTextureBufferShader.setupUniform( width, height, terminal.getPalette(), !monitor.isColour() );
buffer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION );
buffer.pos( -xMargin, -yMargin, 0 ).endVertex();
buffer.pos( -xMargin, pixelHeight + yMargin, 0 ).endVertex();
buffer.pos( pixelWidth + xMargin, -yMargin, 0 ).endVertex();
buffer.pos( pixelWidth + xMargin, pixelHeight + yMargin, 0 ).endVertex();
tessellator.draw();
OpenGlHelper.glUseProgram( 0 );
break;
}
case VBO:
{
VertexBuffer vbo = monitor.buffer;

View File

@@ -16,11 +16,11 @@ import static dan200.computercraft.api.lua.ArgumentHelper.*;
public class RedstoneAPI implements ILuaAPI
{
private IAPIEnvironment m_environment;
private final IAPIEnvironment environment;
public RedstoneAPI( IAPIEnvironment environment )
{
m_environment = environment;
this.environment = environment;
}
@Override
@@ -63,31 +63,31 @@ public class RedstoneAPI implements ILuaAPI
// setOutput
ComputerSide side = parseSide( args );
boolean output = getBoolean( args, 1 );
m_environment.setOutput( side, output ? 15 : 0 );
environment.setOutput( side, output ? 15 : 0 );
return null;
}
case 2: // getOutput
return new Object[] { m_environment.getOutput( parseSide( args ) ) > 0 };
return new Object[] { environment.getOutput( parseSide( args ) ) > 0 };
case 3: // getInput
return new Object[] { m_environment.getInput( parseSide( args ) ) > 0 };
return new Object[] { environment.getInput( parseSide( args ) ) > 0 };
case 4:
{
// setBundledOutput
ComputerSide side = parseSide( args );
int output = getInt( args, 1 );
m_environment.setBundledOutput( side, output );
environment.setBundledOutput( side, output );
return null;
}
case 5: // getBundledOutput
return new Object[] { m_environment.getBundledOutput( parseSide( args ) ) };
return new Object[] { environment.getBundledOutput( parseSide( args ) ) };
case 6: // getBundledInput
return new Object[] { m_environment.getBundledInput( parseSide( args ) ) };
return new Object[] { environment.getBundledInput( parseSide( args ) ) };
case 7:
{
// testBundledInput
ComputerSide side = parseSide( args );
int mask = getInt( args, 1 );
int input = m_environment.getBundledInput( side );
int input = environment.getBundledInput( side );
return new Object[] { (input & mask) == mask };
}
case 8:
@@ -100,15 +100,15 @@ public class RedstoneAPI implements ILuaAPI
{
throw new LuaException( "Expected number in range 0-15" );
}
m_environment.setOutput( side, output );
environment.setOutput( side, output );
return null;
}
case 10:
case 11: // getAnalogOutput/getAnalogueOutput
return new Object[] { m_environment.getOutput( parseSide( args ) ) };
return new Object[] { environment.getOutput( parseSide( args ) ) };
case 12:
case 13: // getAnalogInput/getAnalogueInput
return new Object[] { m_environment.getInput( parseSide( args ) ) };
return new Object[] { environment.getInput( parseSide( args ) ) };
default:
return null;
}

View File

@@ -37,12 +37,8 @@ public abstract class HandleGeneric implements ILuaObject
{
m_open = false;
Closeable closeable = m_closable;
if( closeable != null )
{
IoUtil.closeQuietly( closeable );
m_closable = null;
}
IoUtil.closeQuietly( m_closable );
m_closable = null;
}
/**

View File

@@ -106,7 +106,7 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
protected static <T extends Closeable> T closeCloseable( T closeable )
{
if( closeable != null ) IoUtil.closeQuietly( closeable );
IoUtil.closeQuietly( closeable );
return null;
}

View File

@@ -221,7 +221,7 @@ public class Websocket extends Resource<Websocket>
WeakReference<WebsocketHandle> websocketHandleRef = websocketHandle;
WebsocketHandle websocketHandle = websocketHandleRef == null ? null : websocketHandleRef.get();
if( websocketHandle != null ) IoUtil.closeQuietly( websocketHandle );
IoUtil.closeQuietly( websocketHandle );
this.websocketHandle = null;
}

View File

@@ -13,6 +13,7 @@ import javax.annotation.Nullable;
import java.util.TreeSet;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
@@ -49,11 +50,11 @@ import static dan200.computercraft.core.computer.TimeoutState.TIMEOUT;
public final class ComputerThread
{
/**
* How often the computer thread monitor should run, in milliseconds.
* How often the computer thread monitor should run.
*
* @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.
@@ -76,6 +77,13 @@ public final class ComputerThread
*/
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.
*/
@@ -102,6 +110,8 @@ public final class ComputerThread
private static final ReentrantLock computerLock = new ReentrantLock();
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.
@@ -135,7 +145,7 @@ public final class ComputerThread
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.computer_threads];
// 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 );
boolean wasBusy = isBusy();
// Add to the queue, and signal the workers.
computerQueue.add( executor );
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
{
@@ -346,6 +361,17 @@ public final class ComputerThread
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
* abort limit.
@@ -357,76 +383,93 @@ public final class ComputerThread
@Override
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;
if( currentRunners != null )
checkRunners();
}
}
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];
// If we've no runner, skip.
if( runner == null || runner.owner == null || !runner.owner.isAlive() )
{
if( !running ) continue;
// Mark the old runner as dead and start a new one.
ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!",
runner != null && runner.owner != null ? runner.owner.getName() : runner );
if( runner != null ) runner.running = false;
runnerFactory.newThread( runners[i] = new TaskRunner() ).start();
}
// If the runner has no work, skip
ComputerExecutor executor = runner.currentExecutor.get();
if( executor == null ) continue;
// If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT),
// then we can let the Lua machine do its work.
long afterStart = executor.timeout.nanoCumulative();
long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT;
if( afterHardAbort < 0 ) continue;
// Set the hard abort flag.
executor.timeout.hardAbort();
executor.abort();
if( afterHardAbort >= ABORT_TIMEOUT )
{
// If we've hard aborted but we're still not dead, dump the stack trace and interrupt
// the task.
timeoutTask( executor, runner.owner, afterStart );
runner.owner.interrupt();
}
else if( afterHardAbort >= ABORT_TIMEOUT * 2 )
{
// If we've hard aborted and interrupted, and we're still not dead, then mark the runner
// as dead, finish off the task, and spawn a new runner.
timeoutTask( executor, runner.owner, afterStart );
runner.running = false;
runner.owner.interrupt();
ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null );
if( thisExecutor != null ) afterWork( runner, executor );
synchronized( threadLock )
{
if( running && runners.length > i && runners[i] == runner )
{
runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start();
}
}
}
runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start();
}
}
}
}
catch( InterruptedException ignored )
{
else if( afterHardAbort >= ABORT_TIMEOUT )
{
// 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
{
Thread owner;
long lastReport = Long.MIN_VALUE;
volatile boolean running = true;
final AtomicReference<ComputerExecutor> currentExecutor = new AtomicReference<>();
@@ -460,6 +504,7 @@ public final class ComputerThread
computerLock.lockInterruptibly();
try
{
idleWorkers.incrementAndGet();
while( computerQueue.isEmpty() ) hasWork.await();
executor = computerQueue.pollFirst();
assert executor != null : "hasWork should ensure we never receive null work";
@@ -467,6 +512,7 @@ public final class ComputerThread
finally
{
computerLock.unlock();
idleWorkers.decrementAndGet();
}
}
catch( InterruptedException ignored )
@@ -514,27 +560,32 @@ public final class ComputerThread
}
}
}
}
private static void timeoutTask( ComputerExecutor executor, Thread thread, long time )
{
if( !ComputerCraft.logPeripheralErrors ) 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() )
private void reportTimeout( ComputerExecutor executor, long time )
{
builder.append( "\n at " ).append( element );
}
if( !ComputerCraft.logPeripheralErrors ) 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

@@ -86,7 +86,7 @@ public final class TimeoutState
/**
* 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
// need to handle overflow.
@@ -153,7 +153,7 @@ public final class TimeoutState
*
* @see #nanoCumulative()
*/
void pauseTimer()
synchronized void pauseTimer()
{
// We set the cumulative time to difference between current time and "nominal start time".
cumulativeElapsed = System.nanoTime() - cumulativeStart;
@@ -163,7 +163,7 @@ public final class TimeoutState
/**
* Resets the cumulative time and resets the abort flags.
*/
void stopTimer()
synchronized void stopTimer()
{
cumulativeElapsed = 0;
paused = softAbort = hardAbort = false;

View File

@@ -366,8 +366,7 @@ public class FileSystem
Reference<?> ref;
while( (ref = m_openFileQueue.poll()) != null )
{
Closeable file = m_openFiles.remove( ref );
if( file != null ) IoUtil.closeQuietly( file );
IoUtil.closeQuietly( m_openFiles.remove( ref ) );
}
}
}

View File

@@ -445,24 +445,9 @@ public class CobaltLuaMachine implements ILuaMachine
// We check our current pause/abort state every 128 instructions.
if( (count = (count + 1) & 127) == 0 )
{
// If we've been hard aborted or closed then abort.
if( timeout.isHardAborted() || m_state == null ) throw HardAbortError.INSTANCE;
timeout.refresh();
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();
if( timeout.isPaused() ) handlePause( ds, di );
if( timeout.isSoftAborted() ) handleSoftAbort();
}
super.onInstruction( ds, di, pc );
@@ -471,13 +456,10 @@ public class CobaltLuaMachine implements ILuaMachine
@Override
public void poll() throws LuaError
{
// If we've been hard aborted or closed then abort.
LuaState state = m_state;
if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE;
timeout.refresh();
if( timeout.isPaused() ) LuaThread.suspendBlocking( state );
handleSoftAbort();
if( timeout.isSoftAborted() ) handleSoftAbort();
}
private void resetPaused( DebugState ds, DebugFrame di )
@@ -491,11 +473,24 @@ public class CobaltLuaMachine implements ILuaMachine
private void handleSoftAbort() throws LuaError
{
// If we already thrown our soft abort error then don't do it again.
if( !timeout.isSoftAborted() || thrownSoftAbort ) return;
if( thrownSoftAbort ) return;
thrownSoftAbort = true;
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 class CobaltLuaContext implements ILuaContext

View File

@@ -5,18 +5,20 @@
*/
package dan200.computercraft.core.terminal;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
public class Terminal
{
private static final String base16 = "0123456789abcdef";
private int m_cursorX;
private int m_cursorY;
private boolean m_cursorBlink;
private int m_cursorColour;
private int m_cursorBackgroundColour;
private int m_cursorX = 0;
private int m_cursorY = 0;
private boolean m_cursorBlink = false;
private int m_cursorColour = 0;
private int m_cursorBackgroundColour = 15;
private int m_width;
private int m_height;
@@ -25,9 +27,9 @@ public class Terminal
private TextBuffer[] m_textColour;
private TextBuffer[] m_backgroundColour;
private final Palette m_palette;
private final Palette m_palette = new Palette();
private boolean m_changed;
private boolean m_changed = false;
private final Runnable onChanged;
public Terminal( int width, int height )
@@ -41,9 +43,6 @@ public class Terminal
m_height = height;
onChanged = changedCallback;
m_cursorColour = 0;
m_cursorBackgroundColour = 15;
m_text = new TextBuffer[m_height];
m_textColour = new TextBuffer[m_height];
m_backgroundColour = new TextBuffer[m_height];
@@ -53,14 +52,6 @@ public class Terminal
m_textColour[i] = new TextBuffer( base16.charAt( m_cursorColour ), m_width );
m_backgroundColour[i] = new TextBuffer( base16.charAt( m_cursorBackgroundColour ), m_width );
}
m_cursorX = 0;
m_cursorY = 0;
m_cursorBlink = false;
m_changed = false;
m_palette = new Palette();
}
public synchronized void reset()
@@ -336,6 +327,62 @@ public class Terminal
m_changed = false;
}
public synchronized void write( PacketBuffer buffer )
{
buffer.writeInt( m_cursorX );
buffer.writeInt( m_cursorY );
buffer.writeBoolean( m_cursorBlink );
buffer.writeByte( m_cursorBackgroundColour << 4 | m_cursorColour );
for( int y = 0; y < m_height; y++ )
{
TextBuffer text = m_text[y];
TextBuffer textColour = m_textColour[y];
TextBuffer backColour = m_backgroundColour[y];
for( int x = 0; x < m_width; x++ )
{
buffer.writeByte( text.charAt( x ) & 0xFF );
buffer.writeByte( getColour(
backColour.charAt( x ), Colour.Black ) << 4 |
getColour( textColour.charAt( x ), Colour.White )
);
}
}
m_palette.write( buffer );
}
public synchronized void read( PacketBuffer buffer )
{
m_cursorX = buffer.readInt();
m_cursorY = buffer.readInt();
m_cursorBlink = buffer.readBoolean();
byte cursorColour = buffer.readByte();
m_cursorBackgroundColour = (cursorColour >> 4) & 0xF;
m_cursorColour = cursorColour & 0xF;
for( int y = 0; y < m_height; y++ )
{
TextBuffer text = m_text[y];
TextBuffer textColour = m_textColour[y];
TextBuffer backColour = m_backgroundColour[y];
for( int x = 0; x < m_width; x++ )
{
text.setChar( x, (char) (buffer.readByte() & 0xFF) );
byte colour = buffer.readByte();
backColour.setChar( x, base16.charAt( (colour >> 4) & 0xF ) );
textColour.setChar( x, base16.charAt( colour & 0xF ) );
}
}
m_palette.read( buffer );
setChanged();
}
public synchronized NBTTagCompound writeToNBT( NBTTagCompound nbt )
{
nbt.setInteger( "term_cursorX", m_cursorX );
@@ -349,10 +396,8 @@ public class Terminal
nbt.setString( "term_textColour_" + n, m_textColour[n].toString() );
nbt.setString( "term_textBgColour_" + n, m_backgroundColour[n].toString() );
}
if( m_palette != null )
{
m_palette.writeToNBT( nbt );
}
m_palette.writeToNBT( nbt );
return nbt;
}
@@ -382,10 +427,15 @@ public class Terminal
m_backgroundColour[n].write( nbt.getString( "term_textBgColour_" + n ) );
}
}
if( m_palette != null )
{
m_palette.readFromNBT( nbt );
}
m_palette.readFromNBT( nbt );
setChanged();
}
public static int getColour( char c, Colour def )
{
if( c >= '0' && c <= '9' ) return c - '0';
if( c >= 'a' && c <= 'f' ) return c - 'a' + 10;
return 15 - def.ordinal();
}
}

View File

@@ -47,6 +47,7 @@ public final class Config
private static Property defaultComputerSettings;
private static Property debugEnabled;
private static Property logComputerErrors;
private static Property commandRequireCreative;
private static Property computerThreads;
private static Property maxMainGlobalTime;
@@ -71,6 +72,7 @@ public final class Config
private static Property modemHighAltitudeRangeDuringStorm;
private static Property maxNotesPerTick;
private static Property monitorRenderer;
private static Property monitorBandwidth;
private static Property turtlesNeedFuel;
private static Property turtleFuelLimit;
@@ -119,10 +121,14 @@ public final class Config
logComputerErrors.setComment( "Log exceptions thrown by peripherals and other Lua objects.\n" +
"This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." );
commandRequireCreative = config.get( CATEGORY_GENERAL, "command_require_creative", ComputerCraft.commandRequireCreative );
commandRequireCreative.setComment( "Require players to be in creative mode and be opped in order to interact with command computers." +
"This is the default behaviour for vanilla's Command blocks." );
setOrder(
CATEGORY_GENERAL,
computerSpaceLimit, floppySpaceLimit, maximumFilesOpen,
disableLua51Features, defaultComputerSettings, debugEnabled, logComputerErrors
disableLua51Features, defaultComputerSettings, debugEnabled, logComputerErrors, commandRequireCreative
);
}
@@ -271,10 +277,21 @@ public final class Config
"monitors have performance issues, you may wish to experiment with alternative renderers." );
monitorRenderer.setValidValues( MonitorRenderer.NAMES );
monitorBandwidth = config.get( CATEGORY_PERIPHERAL, "monitor_bandwidth", (int) ComputerCraft.monitorBandwidth );
monitorBandwidth.setComment( "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 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." );
monitorBandwidth.setValidValues( MonitorRenderer.NAMES );
monitorBandwidth.setMinValue( 0 );
setOrder(
CATEGORY_PERIPHERAL,
commandBlockEnabled, modemRange, modemHighAltitudeRange, modemRangeDuringStorm, modemHighAltitudeRangeDuringStorm, maxNotesPerTick,
monitorRenderer
monitorRenderer, monitorBandwidth
);
}
@@ -441,6 +458,7 @@ public final class Config
ComputerCraft.default_computer_settings = defaultComputerSettings.getString();
ComputerCraft.debug_enable = debugEnabled.getBoolean();
ComputerCraft.logPeripheralErrors = logComputerErrors.getBoolean();
ComputerCraft.commandRequireCreative = commandRequireCreative.getBoolean();
// Execution
ComputerCraft.computer_threads = computerThreads.getInt();
@@ -468,6 +486,7 @@ public final class Config
ComputerCraft.modem_rangeDuringStorm = Math.min( modemRangeDuringStorm.getInt(), MODEM_MAX_RANGE );
ComputerCraft.modem_highAltitudeRangeDuringStorm = Math.min( modemHighAltitudeRangeDuringStorm.getInt(), MODEM_MAX_RANGE );
ComputerCraft.monitorRenderer = MonitorRenderer.ofString( monitorRenderer.getString() );
ComputerCraft.monitorBandwidth = Math.max( 0, monitorBandwidth.getLong() );
// Turtles
ComputerCraft.turtlesNeedFuel = turtlesNeedFuel.getBoolean();

View File

@@ -117,4 +117,3 @@ public interface TableFormatter
return rowId - table.getId();
}
}

View File

@@ -6,7 +6,7 @@
package dan200.computercraft.shared.common;
import dan200.computercraft.core.terminal.Terminal;
import net.minecraft.nbt.NBTTagCompound;
import dan200.computercraft.shared.network.client.TerminalState;
public class ClientTerminal implements ITerminal
{
@@ -46,14 +46,13 @@ public class ClientTerminal implements ITerminal
return m_colour;
}
public void readDescription( NBTTagCompound nbt )
public void read( TerminalState state )
{
m_colour = nbt.getBoolean( "colour" );
if( nbt.hasKey( "terminal" ) )
m_colour = state.colour;
if( state.hasTerminal() )
{
NBTTagCompound terminal = nbt.getCompoundTag( "terminal" );
resizeTerminal( terminal.getInteger( "term_width" ), terminal.getInteger( "term_height" ) );
m_terminal.readFromNBT( terminal );
resizeTerminal( state.width, state.height );
state.apply( m_terminal );
}
else
{

View File

@@ -6,7 +6,7 @@
package dan200.computercraft.shared.common;
import dan200.computercraft.core.terminal.Terminal;
import net.minecraft.nbt.NBTTagCompound;
import dan200.computercraft.shared.network.client.TerminalState;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -69,8 +69,6 @@ public class ServerTerminal implements ITerminal
return m_terminalChangedLastFrame;
}
// ITerminal implementation
@Override
public Terminal getTerminal()
{
@@ -83,18 +81,8 @@ public class ServerTerminal implements ITerminal
return m_colour;
}
// Networking stuff
public void writeDescription( NBTTagCompound nbt )
public TerminalState write()
{
nbt.setBoolean( "colour", m_colour );
if( m_terminal != null )
{
NBTTagCompound terminal = new NBTTagCompound();
terminal.setInteger( "term_width", m_terminal.getWidth() );
terminal.setInteger( "term_height", m_terminal.getHeight() );
m_terminal.writeToNBT( terminal );
nbt.setTag( "terminal", terminal );
}
return new TerminalState( m_colour, m_terminal );
}
}

View File

@@ -128,7 +128,7 @@ public abstract class BlockComputerBase extends BlockDirectional
if( tile instanceof TileComputerBase )
{
TileComputerBase computer = (TileComputerBase) tile;
if( !player.capabilities.isCreativeMode || computer.getLabel() != null )
if( !player.capabilities.isCreativeMode || computer.getLabel() != null || computer.getComputerID() != -1 )
{
spawnAsEntity( world, pos, getItem( computer ) );
}

View File

@@ -45,4 +45,3 @@ public enum ComputerState implements IStringSerializable
return ordinal < 0 || ordinal >= VALUES.length ? ComputerState.Off : VALUES[ordinal];
}
}

View File

@@ -5,6 +5,7 @@
*/
package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.computer.apis.CommandAPI;
import dan200.computercraft.shared.computer.core.ServerComputer;
import io.netty.buffer.ByteBuf;
@@ -167,6 +168,11 @@ public class TileCommandComputer extends TileComputer
@Override
public boolean isUsable( EntityPlayer player, boolean ignoreRange )
{
return isUsable( player ) && super.isUsable( player, ignoreRange );
}
public static boolean isUsable( EntityPlayer player )
{
MinecraftServer server = player.getServer();
if( server == null || !server.isCommandBlockEnabled() )
@@ -174,14 +180,12 @@ public class TileCommandComputer extends TileComputer
player.sendMessage( new TextComponentTranslation( "advMode.notEnabled" ) );
return false;
}
else if( !player.canUseCommandBlock() )
else if( ComputerCraft.commandRequireCreative ? !player.canUseCommandBlock() : !server.getPlayerList().canSendCommands( player.getGameProfile() ) )
{
player.sendMessage( new TextComponentTranslation( "advMode.notAllowed" ) );
return false;
}
else
{
return super.isUsable( player, ignoreRange );
}
return true;
}
}

View File

@@ -155,9 +155,7 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
protected IMessage createTerminalPacket()
{
NBTTagCompound tagCompound = new NBTTagCompound();
writeDescription( tagCompound );
return new ComputerTerminalClientMessage( getInstanceID(), tagCompound );
return new ComputerTerminalClientMessage( getInstanceID(), write() );
}
public void broadcastState( boolean force )

View File

@@ -6,11 +6,10 @@
package dan200.computercraft.shared.computer.inventory;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.computer.core.*;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.text.TextComponentTranslation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -46,19 +45,9 @@ public class ContainerViewComputer extends Container implements IContainerComput
}
// If we're a command computer then ensure we're in creative
if( serverComputer.getFamily() == ComputerFamily.Command )
if( serverComputer.getFamily() == ComputerFamily.Command && !TileCommandComputer.isUsable( player ) )
{
MinecraftServer server = player.getServer();
if( server == null || !server.isCommandBlockEnabled() )
{
player.sendMessage( new TextComponentTranslation( "advMode.notEnabled" ) );
return false;
}
else if( !player.canUseCommandBlock() )
{
player.sendMessage( new TextComponentTranslation( "advMode.notAllowed" ) );
return false;
}
return false;
}
}

View File

@@ -19,9 +19,7 @@ public final class ComputerItemFactory
@Nonnull
public static ItemStack create( TileComputer tile )
{
String label = tile.getLabel();
int id = label != null ? tile.getComputerID() : -1;
return create( id, label, tile.getFamily() );
return create( tile.getComputerID(), tile.getLabel(), tile.getFamily() );
}
@Nonnull

View File

@@ -39,7 +39,7 @@ public abstract class ItemComputerBase extends ItemBlock implements IComputerIte
@Override
public void addInformation( @Nonnull ItemStack stack, @Nullable World world, @Nonnull List<String> list, @Nonnull ITooltipFlag flag )
{
if( flag.isAdvanced() )
if( flag.isAdvanced() || getLabel( stack ) == null )
{
int id = getComputerID( stack );
if( id >= 0 ) list.add( StringUtil.translateFormatted( "gui.computercraft.tooltip.computer_id", id ) );

View File

@@ -80,7 +80,7 @@ public class ItemDiskLegacy extends Item implements IMedia, IColouredItem
@Override
public void addInformation( @Nonnull ItemStack stack, World world, List<String> list, ITooltipFlag flag )
{
if( flag.isAdvanced() )
if( flag.isAdvanced() || getLabel( stack ) == null )
{
int id = getDiskID( stack );
if( id >= 0 ) list.add( StringUtil.translateFormatted( "gui.computercraft.tooltip.disk_id", id ) );

View File

@@ -45,6 +45,7 @@ public final class NetworkHandler
registerMainThread( 12, Side.CLIENT, ComputerDeletedClientMessage::new );
registerMainThread( 13, Side.CLIENT, ComputerTerminalClientMessage::new );
registerMainThread( 14, Side.CLIENT, PlayRecordClientMessage::new );
registerMainThread( 15, Side.CLIENT, MonitorClientMessage::new );
}
public static void sendToPlayer( EntityPlayer player, IMessage packet )
@@ -67,6 +68,11 @@ public final class NetworkHandler
network.sendToAllAround( packet, point );
}
public static void sendToAllTracking( IMessage packet, NetworkRegistry.TargetPoint point )
{
network.sendToAllTracking( packet, point );
}
/**
* /**
* Register packet, and a thread-unsafe handler for it.

View File

@@ -5,8 +5,6 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
@@ -14,12 +12,12 @@ import javax.annotation.Nonnull;
public class ComputerTerminalClientMessage extends ComputerClientMessage
{
private NBTTagCompound tag;
private TerminalState state;
public ComputerTerminalClientMessage( int instanceId, NBTTagCompound tag )
public ComputerTerminalClientMessage( int instanceId, TerminalState state )
{
super( instanceId );
this.tag = tag;
this.state = state;
}
public ComputerTerminalClientMessage()
@@ -30,19 +28,19 @@ public class ComputerTerminalClientMessage extends ComputerClientMessage
public void toBytes( @Nonnull PacketBuffer buf )
{
super.toBytes( buf );
buf.writeCompoundTag( tag ); // TODO: Do we need to compress this?
state.write( buf );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
super.fromBytes( buf );
tag = NBTUtil.readCompoundTag( buf );
state = new TerminalState( buf );
}
@Override
public void handle( MessageContext context )
{
getComputer().readDescription( tag );
getComputer().read( state );
}
}

View File

@@ -0,0 +1,60 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.network.PacketBuffer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import javax.annotation.Nonnull;
public class MonitorClientMessage implements NetworkMessage
{
private BlockPos pos;
private TerminalState state;
public MonitorClientMessage( BlockPos pos, TerminalState state )
{
this.pos = pos;
this.state = state;
}
public MonitorClientMessage()
{
}
@Override
public void toBytes( @Nonnull PacketBuffer buf )
{
buf.writeBlockPos( pos );
state.write( buf );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
pos = buf.readBlockPos();
state = new TerminalState( buf );
}
@Override
public void handle( MessageContext context )
{
EntityPlayerSP player = Minecraft.getMinecraft().player;
if( player == null || player.world == null ) return;
TileEntity te = player.world.getTileEntity( pos );
if( !(te instanceof TileMonitor) ) return;
((TileMonitor) te).read( state );
}
}

View File

@@ -0,0 +1,183 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.IoUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import net.minecraft.network.PacketBuffer;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* A snapshot of a terminal's state.
*
* This is somewhat memory inefficient (we build a buffer, only to write it elsewhere), however it means we get a
* complete and accurate description of a terminal, which avoids a lot of complexities with resizing terminals, dirty
* states, etc...
*/
public class TerminalState
{
public final boolean colour;
public final int width;
public final int height;
private final boolean compress;
@Nullable
private final ByteBuf buffer;
private ByteBuf compressed;
public TerminalState( boolean colour, @Nullable Terminal terminal )
{
this( colour, terminal, true );
}
public TerminalState( boolean colour, @Nullable Terminal terminal, boolean compress )
{
this.colour = colour;
this.compress = compress;
if( terminal == null )
{
this.width = this.height = 0;
this.buffer = null;
}
else
{
this.width = terminal.getWidth();
this.height = terminal.getHeight();
ByteBuf buf = this.buffer = Unpooled.buffer();
terminal.write( new PacketBuffer( buf ) );
}
}
public TerminalState( PacketBuffer buf )
{
this.colour = buf.readBoolean();
this.compress = buf.readBoolean();
if( buf.readBoolean() )
{
this.width = buf.readVarInt();
this.height = buf.readVarInt();
int length = buf.readVarInt();
this.buffer = readCompressed( buf, length, compress );
}
else
{
this.width = this.height = 0;
this.buffer = null;
}
}
public void write( PacketBuffer buf )
{
buf.writeBoolean( colour );
buf.writeBoolean( compress );
buf.writeBoolean( buffer != null );
if( buffer != null )
{
buf.writeVarInt( width );
buf.writeVarInt( height );
ByteBuf sendBuffer = getCompressed();
buf.writeVarInt( sendBuffer.readableBytes() );
buf.writeBytes( sendBuffer, sendBuffer.readerIndex(), sendBuffer.readableBytes() );
}
}
public boolean hasTerminal()
{
return buffer != null;
}
public int size()
{
return buffer == null ? 0 : buffer.readableBytes();
}
public void apply( Terminal terminal )
{
if( buffer == null ) throw new NullPointerException( "buffer" );
terminal.read( new PacketBuffer( buffer ) );
}
private ByteBuf getCompressed()
{
if( buffer == null ) throw new NullPointerException( "buffer" );
if( !compress ) return buffer;
if( compressed != null ) return compressed;
ByteBuf compressed = Unpooled.directBuffer();
OutputStream stream = null;
try
{
stream = new GZIPOutputStream( new ByteBufOutputStream( compressed ) );
stream.write( buffer.array(), buffer.arrayOffset(), buffer.readableBytes() );
}
catch( IOException e )
{
throw new UncheckedIOException( e );
}
finally
{
IoUtil.closeQuietly( stream );
}
return this.compressed = compressed;
}
private static ByteBuf readCompressed( ByteBuf buf, int length, boolean compress )
{
if( compress )
{
ByteBuf buffer = Unpooled.buffer();
InputStream stream = null;
try
{
stream = new GZIPInputStream( new ByteBufInputStream( buf, length ) );
byte[] swap = new byte[8192];
while( true )
{
int bytes = stream.read( swap );
if( bytes == -1 ) break;
buffer.writeBytes( swap, 0, bytes );
}
}
catch( IOException e )
{
throw new UncheckedIOException( e );
}
finally
{
IoUtil.closeQuietly( stream );
}
return buffer;
}
else
{
ByteBuf buffer = Unpooled.buffer( length );
buf.readBytes( buffer, length );
return buffer;
}
}
}

View File

@@ -24,6 +24,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
@@ -444,4 +445,11 @@ public class TileCable extends TileGeneric implements IPeripheralTile
IBlockState state = getBlockState();
return BlockCable.getPeripheralType( state );
}
@Nonnull
@Override
public AxisAlignedBB getRenderBoundingBox()
{
return Block.FULL_BLOCK_AABB.offset( getPos() );
}
}

View File

@@ -8,10 +8,16 @@ package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.shared.common.ClientTerminal;
import net.minecraft.client.renderer.GLAllocation;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.util.HashSet;
import java.util.Iterator;
@@ -26,6 +32,8 @@ public final class ClientMonitor extends ClientTerminal
public long lastRenderFrame = -1;
public BlockPos lastRenderPos = null;
public int tboBuffer;
public int tboTexture;
public VertexBuffer buffer;
public int displayList = 0;
@@ -43,7 +51,7 @@ public final class ClientMonitor extends ClientTerminal
/**
* Create the appropriate buffer if needed.
*
* @param renderer The renderer to use. This can be fetched from {@link #renderer()}.
* @param renderer The renderer to use. This can be fetched from {@link MonitorRenderer#current()}.
* @return If a buffer was created. This will return {@code false} if we already have an appropriate buffer,
* or this mode does not require one.
*/
@@ -52,6 +60,26 @@ public final class ClientMonitor extends ClientTerminal
{
switch( renderer )
{
case TBO:
{
if( tboBuffer != 0 ) return false;
deleteBuffers();
tboBuffer = OpenGlHelper.glGenBuffers();
OpenGlHelper.glBindBuffer( GL31.GL_TEXTURE_BUFFER, tboBuffer );
GL15.glBufferData( GL31.GL_TEXTURE_BUFFER, 0, GL15.GL_STATIC_DRAW );
tboTexture = GlStateManager.generateTexture();
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, tboTexture );
GL31.glTexBuffer( GL31.GL_TEXTURE_BUFFER, GL30.GL_R8, tboBuffer );
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, 0 );
OpenGlHelper.glBindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
addMonitor();
return true;
}
case VBO:
if( buffer != null ) return false;
@@ -59,6 +87,7 @@ public final class ClientMonitor extends ClientTerminal
buffer = new VertexBuffer( FixedWidthFontRenderer.POSITION_COLOR_TEX );
addMonitor();
return true;
case DISPLAY_LIST:
if( displayList != 0 ) return false;
@@ -82,6 +111,19 @@ public final class ClientMonitor extends ClientTerminal
private void deleteBuffers()
{
if( tboBuffer != 0 )
{
OpenGlHelper.glDeleteBuffers( tboBuffer );
tboBuffer = 0;
}
if( tboTexture != 0 )
{
GlStateManager.deleteTexture( tboTexture );
tboTexture = 0;
}
if( buffer != null )
{
buffer.deleteGlBuffers();
@@ -98,7 +140,7 @@ public final class ClientMonitor extends ClientTerminal
@SideOnly( Side.CLIENT )
public void destroy()
{
if( buffer != null || displayList != 0 )
if( tboBuffer != 0 || buffer != null || displayList != 0 )
{
synchronized( allMonitors )
{

View File

@@ -9,6 +9,7 @@ package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import net.minecraft.client.renderer.OpenGlHelper;
import org.lwjgl.opengl.GLContext;
import javax.annotation.Nonnull;
import java.util.Locale;
@@ -27,7 +28,14 @@ public enum MonitorRenderer
BEST,
/**
* Render using VBOs. This is the default when supported.
* Render using texture buffer objects.
*
* @see org.lwjgl.opengl.GL31#glTexBuffer(int, int, int)
*/
TBO,
/**
* Render using VBOs.
*
* @see net.minecraft.client.renderer.vertex.VertexBuffer
*/
@@ -84,6 +92,16 @@ public enum MonitorRenderer
{
case BEST:
return best();
case TBO:
checkCapabilities();
if( !textureBuffer )
{
ComputerCraft.log.warn( "Texture buffers are not supported on your graphics card. Falling back to default." );
ComputerCraft.monitorRenderer = BEST;
return best();
}
return TBO;
case VBO:
if( !OpenGlHelper.vboSupported )
{
@@ -100,6 +118,20 @@ public enum MonitorRenderer
private static MonitorRenderer best()
{
return OpenGlHelper.vboSupported ? VBO : DISPLAY_LIST;
checkCapabilities();
if( textureBuffer ) return TBO;
if( OpenGlHelper.vboSupported ) return VBO;
return DISPLAY_LIST;
}
private static boolean initialised = false;
private static boolean textureBuffer = false;
private static void checkCapabilities()
{
if( initialised ) return;
textureBuffer = GLContext.getCapabilities().OpenGL31;
initialised = true;
}
}

View File

@@ -0,0 +1,102 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.MonitorClientMessage;
import dan200.computercraft.shared.network.client.TerminalState;
import net.minecraft.server.management.PlayerChunkMapEntry;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.event.world.ChunkWatchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import java.util.ArrayDeque;
import java.util.Queue;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID )
public final class MonitorWatcher
{
private static final Queue<TileMonitor> watching = new ArrayDeque<>();
private MonitorWatcher()
{
}
static void enqueue( TileMonitor monitor )
{
if( monitor.enqueued ) return;
monitor.enqueued = true;
monitor.cached = null;
watching.add( monitor );
}
@SubscribeEvent
public static void onWatch( ChunkWatchEvent.Watch event )
{
Chunk chunk = event.getChunkInstance();
if( chunk == null ) return;
for( TileEntity te : chunk.getTileEntityMap().values() )
{
// Find all origin monitors who are not already on the queue.
if( !(te instanceof TileMonitor) ) continue;
TileMonitor monitor = (TileMonitor) te;
ServerMonitor serverMonitor = getMonitor( monitor );
if( serverMonitor == null || monitor.enqueued ) continue;
// We use the cached terminal state if available - this is guaranteed to
TerminalState state = monitor.cached;
if( state == null ) state = monitor.cached = serverMonitor.write();
NetworkHandler.sendToPlayer( event.getPlayer(), new MonitorClientMessage( monitor.getPos(), state ) );
}
}
@SubscribeEvent
public static void onTick( TickEvent.ServerTickEvent event )
{
if( event.phase != TickEvent.Phase.END ) return;
long limit = ComputerCraft.monitorBandwidth;
boolean obeyLimit = limit > 0;
TileMonitor tile;
while( (!obeyLimit || limit > 0) && (tile = watching.poll()) != null )
{
tile.enqueued = false;
ServerMonitor monitor = getMonitor( tile );
if( monitor == null ) continue;
BlockPos pos = tile.getPos();
World world = tile.getWorld();
WorldServer serverWorld = world instanceof WorldServer ? (WorldServer) world : DimensionManager.getWorld( world.provider.getDimension() );
PlayerChunkMapEntry entry = serverWorld.getPlayerChunkMap().getEntry( pos.getX() >> 4, pos.getZ() >> 4 );
if( entry == null || entry.getWatchingPlayers().isEmpty() ) continue;
NetworkRegistry.TargetPoint point = new NetworkRegistry.TargetPoint( world.provider.getDimension(), pos.getX(), pos.getY(), pos.getZ(), 0 );
TerminalState state = tile.cached = monitor.write();
NetworkHandler.sendToAllTracking( new MonitorClientMessage( pos, state ), point );
limit -= state.size();
}
}
private static ServerMonitor getMonitor( TileMonitor monitor )
{
return !monitor.isInvalid() && monitor.getXIndex() == 0 && monitor.getYIndex() == 0 ? monitor.getCachedServerMonitor() : null;
}
}

View File

@@ -5,12 +5,14 @@
*/
package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralTile;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.common.ServerTerminal;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.network.client.TerminalState;
import dan200.computercraft.shared.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.common.BlockPeripheral;
import dan200.computercraft.shared.peripheral.common.ITilePeripheral;
@@ -46,6 +48,10 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
private boolean m_destroyed = false;
private boolean visiting = false;
// MonitorWatcher state.
boolean enqueued;
TerminalState cached;
private int m_width = 1;
private int m_height = 1;
private int m_xIndex = 0;
@@ -148,7 +154,7 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
}
}
if( m_serverMonitor.pollTerminalChanged() ) updateBlock();
if( m_serverMonitor.pollTerminalChanged() ) MonitorWatcher.enqueue( this );
}
// IPeripheralTile implementation
@@ -239,11 +245,6 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
nbt.setInteger( "width", m_width );
nbt.setInteger( "height", m_height );
nbt.setInteger( "monitorDir", m_dir );
if( m_xIndex == 0 && m_yIndex == 0 && m_serverMonitor != null )
{
m_serverMonitor.writeDescription( nbt );
}
}
@Override
@@ -273,9 +274,8 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
if( m_xIndex == 0 && m_yIndex == 0 )
{
// If we're the origin terminal then read the description
// If we're the origin terminal then create it.
if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( m_advanced, this );
m_clientMonitor.readDescription( nbt );
}
if( oldXIndex != m_xIndex || oldYIndex != m_yIndex ||
@@ -286,6 +286,19 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
updateBlock();
}
}
public final void read( TerminalState state )
{
if( m_xIndex != 0 || m_yIndex != 0 )
{
ComputerCraft.log.warn( "Receiving monitor state for non-origin terminal at {}", getPos() );
return;
}
if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( m_advanced, this );
m_clientMonitor.read( state );
}
// Sizing and placement stuff
public EnumFacing getDirection()

View File

@@ -148,4 +148,3 @@ public abstract class SpeakerPeripheral implements IPeripheral
return true;
}
}

View File

@@ -211,7 +211,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
@Override
public void addInformation( @Nonnull ItemStack stack, World world, List<String> list, ITooltipFlag flag )
{
if( flag.isAdvanced() )
if( flag.isAdvanced() || getLabel( stack ) == null )
{
int id = getComputerID( stack );
if( id >= 0 ) list.add( StringUtil.translateFormatted( "gui.computercraft.tooltip.computer_id", id ) );

View File

@@ -88,4 +88,3 @@ public class ItemTurtleLegacy extends ItemTurtleBase
return 0;
}
}

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.shared.turtle.items;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.computer.core.ComputerFamily;
@@ -22,18 +23,13 @@ public final class TurtleItemFactory
@Nonnull
public static ItemStack create( ITurtleTile turtle )
{
ITurtleUpgrade leftUpgrade = turtle.getAccess().getUpgrade( TurtleSide.Left );
ITurtleUpgrade rightUpgrade = turtle.getAccess().getUpgrade( TurtleSide.Right );
ITurtleAccess access = turtle.getAccess();
String label = turtle.getLabel();
if( label == null )
{
return create( -1, null, turtle.getColour(), turtle.getFamily(), leftUpgrade, rightUpgrade, 0, turtle.getOverlay() );
}
int id = turtle.getComputerID();
int fuelLevel = turtle.getAccess().getFuelLevel();
return create( id, label, turtle.getColour(), turtle.getFamily(), leftUpgrade, rightUpgrade, fuelLevel, turtle.getOverlay() );
return create(
turtle.getComputerID(), turtle.getLabel(), turtle.getColour(), turtle.getFamily(),
access.getUpgrade( TurtleSide.Left ), access.getUpgrade( TurtleSide.Right ),
access.getFuelLevel(), turtle.getOverlay()
);
}
@Nonnull

View File

@@ -13,11 +13,11 @@ import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
@@ -31,16 +31,16 @@ public final class DropConsumer
private static Function<ItemStack, ItemStack> dropConsumer;
private static List<ItemStack> remainingDrops;
private static WeakReference<World> dropWorld;
private static World dropWorld;
private static AxisAlignedBB dropBounds;
private static WeakReference<Entity> dropEntity;
private static Entity dropEntity;
public static void set( Entity entity, Function<ItemStack, ItemStack> consumer )
{
dropConsumer = consumer;
remainingDrops = new ArrayList<>();
dropEntity = new WeakReference<>( entity );
dropWorld = new WeakReference<>( entity.world );
dropEntity = entity;
dropWorld = entity.world;
dropBounds = new AxisAlignedBB( entity.getPosition() ).grow( 2, 2, 2 );
entity.captureDrops = true;
@@ -51,26 +51,12 @@ public final class DropConsumer
dropConsumer = consumer;
remainingDrops = new ArrayList<>( 2 );
dropEntity = null;
dropWorld = new WeakReference<>( world );
dropWorld = world;
dropBounds = new AxisAlignedBB( pos ).grow( 2, 2, 2 );
}
public static List<ItemStack> clear()
{
if( dropEntity != null )
{
Entity entity = dropEntity.get();
if( entity != null )
{
entity.captureDrops = false;
if( entity.capturedDrops != null )
{
for( EntityItem entityItem : entity.capturedDrops ) handleDrops( entityItem.getItem() );
entity.capturedDrops.clear();
}
}
}
List<ItemStack> remainingStacks = remainingDrops;
dropConsumer = null;
@@ -92,11 +78,20 @@ public final class DropConsumer
public static void onEntitySpawn( EntityJoinWorldEvent event )
{
// Capture any nearby item spawns
if( dropWorld != null && dropWorld.get() == event.getWorld() && event.getEntity() instanceof EntityItem
if( dropWorld == event.getWorld() && event.getEntity() instanceof EntityItem
&& dropBounds.contains( event.getEntity().getPositionVector() ) )
{
handleDrops( ((EntityItem) event.getEntity()).getItem() );
event.setCanceled( true );
}
}
@SubscribeEvent
public static void onLivingDrops( LivingDropsEvent drops )
{
if( dropEntity == null || drops.getEntity() != dropEntity ) return;
for( EntityItem drop : drops.getDrops() ) handleDrops( drop.getItem() );
drops.setCanceled( true );
}
}

View File

@@ -5,6 +5,7 @@
*/
package dan200.computercraft.shared.util;
import javax.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
@@ -12,11 +13,11 @@ public final class IoUtil
{
private IoUtil() {}
public static void closeQuietly( Closeable closeable )
public static void closeQuietly( @Nullable Closeable closeable )
{
try
{
closeable.close();
if( closeable != null ) closeable.close();
}
catch( IOException ignored )
{

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.shared.util;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
public class Palette
{
@@ -78,6 +79,22 @@ public class Palette
};
}
public void write( PacketBuffer buffer )
{
for( double[] colour : colours )
{
for( double channel : colour ) buffer.writeByte( (int) (channel * 0xFF) & 0xFF );
}
}
public void read( PacketBuffer buffer )
{
for( double[] colour : colours )
{
for( int i = 0; i < colour.length; i++ ) colour[i] = (buffer.readByte() & 0xFF) / 255.0;
}
}
public NBTTagCompound writeToNBT( NBTTagCompound nbt )
{
int[] rgb8 = new int[colours.length];

View File

@@ -6,4 +6,3 @@
"modem=true,peripheral=true": { "model": "computercraft:wired_modem_full_on_peripheral" }
}
}

View File

@@ -44,5 +44,5 @@ chat.computercraft.wired_modem.peripheral_disconnected=Perifer enhed "%s" koblet
# Misc tooltips
gui.computercraft.tooltip.copy=Kopier til udklipsholder
gui.computercraft.tooltip.computer_id=(Computer-ID: %s)
gui.computercraft.tooltip.disk_id=(Disk-ID: %s)
gui.computercraft.tooltip.computer_id=Computer-ID: %s
gui.computercraft.tooltip.disk_id=Disk-ID: %s

View File

@@ -148,8 +148,8 @@ tracking_field.computercraft.coroutines_dead.name=Koroutinen gelöscht
# Misc tooltips
gui.computercraft.tooltip.copy=In die Zwischenablage kopieren
gui.computercraft.tooltip.computer_id=(Computer ID: %s)
gui.computercraft.tooltip.disk_id=(Disketten ID: %s)
gui.computercraft.tooltip.computer_id=Computer ID: %s
gui.computercraft.tooltip.disk_id=Disketten ID: %s
# Config options
gui.computercraft:config.computer_space_limit=Speicherplatz von Computern (Bytes)

View File

@@ -148,8 +148,8 @@ tracking_field.computercraft.coroutines_dead.name=Coroutines disposed
# Misc tooltips
gui.computercraft.tooltip.copy=Copy to clipboard
gui.computercraft.tooltip.computer_id=(Computer ID: %s)
gui.computercraft.tooltip.disk_id=(Disk ID: %s)
gui.computercraft.tooltip.computer_id=Computer ID: %s
gui.computercraft.tooltip.disk_id=Disk ID: %s
# Config options
gui.computercraft:config.computer_space_limit=Computer space limit (bytes)
@@ -159,6 +159,7 @@ gui.computercraft:config.disable_lua51_features=Disable Lua 5.1 features
gui.computercraft:config.default_computer_settings=Default Computer settings
gui.computercraft:config.debug_enabled=Enable debug library
gui.computercraft:config.log_computer_errors=Log computer errors
gui.computercraft:config.command_require_creative=Command computers require creative
gui.computercraft:config.execution=Execution
gui.computercraft:config.execution.computer_threads=Computer threads
@@ -187,8 +188,10 @@ gui.computercraft:config.peripheral.modem_high_altitude_range_during_storm=Modem
gui.computercraft:config.peripheral.max_notes_per_tick=Maximum notes that a computer can play at once
gui.computercraft:config.peripheral.monitor_renderer=Monitor renderer
gui.computercraft:config.peripheral.monitor_renderer.best=Best
gui.computercraft:config.peripheral.monitor_renderer.tbo=Texture Buffers
gui.computercraft:config.peripheral.monitor_renderer.vbo=Vertex Buffers
gui.computercraft:config.peripheral.monitor_renderer.display_list=Display Lists
gui.computercraft:config.peripheral.monitor_bandwidth=Monitor bandwidth
gui.computercraft:config.turtle=Turtles
gui.computercraft:config.turtle.need_fuel=Enable fuel

View File

@@ -148,8 +148,8 @@ tracking_field.computercraft.coroutines_dead.name=코루틴 처리됨
# Misc tooltips
gui.computercraft.tooltip.copy=클립보드에 복사
gui.computercraft.tooltip.computer_id=(컴퓨터 ID: %s)
gui.computercraft.tooltip.disk_id=(디스크 ID: %s)
gui.computercraft.tooltip.computer_id=컴퓨터 ID: %s
gui.computercraft.tooltip.disk_id=디스크 ID: %s
# Config options
gui.computercraft:config.computer_space_limit=컴퓨터 공간 제한 (바이트)

View File

@@ -148,8 +148,8 @@ tracking_field.computercraft.coroutines_dead.name=协同处理
# Misc tooltips
gui.computercraft.tooltip.copy=复制到剪贴板
gui.computercraft.tooltip.computer_id=(计算机ID: %s)
gui.computercraft.tooltip.disk_id=(磁盘ID: %s)
gui.computercraft.tooltip.computer_id=计算机ID: %s
gui.computercraft.tooltip.disk_id=磁盘ID: %s
# Config options
gui.computercraft:config.computer_space_limit=计算机空间限制(字节)

View File

@@ -797,6 +797,12 @@ function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
return tEmpty
end
function fs.isDriveRoot(sPath)
expect(1, sPath, "string")
-- Force the root directory to be a mount.
return fs.getDir(sPath) == ".." or fs.getDrive(sPath) ~= fs.getDrive(fs.getDir(sPath))
end
-- Load APIs
local bAPIError = false
local tApis = fs.list("rom/apis")
@@ -932,6 +938,23 @@ settings.define("motd.path", {
description = [[The path to load random messages from. Should be a colon (":") separated string of file paths.]],
type = "string",
})
settings.define("lua.warn_against_use_of_local", {
default = true,
description = [[Print a message when input in the Lua REPL starts with the word 'local'. Local variables defined in the Lua REPL are be inaccessable on the next input.]],
type = "boolean",
})
settings.define("lua.function_args", {
default = true,
description = "Show function arguments when printing functions.",
type = "boolean",
})
settings.define("lua.function_source", {
default = false,
description = "Show where a function was defined when printing functions.",
type = "boolean",
})
if term.isColour() then
settings.define("bios.use_multishell", {
default = true,

View File

@@ -12,6 +12,9 @@
-- [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
error("Cannot load command API on normal computer", 2)
@@ -65,4 +68,13 @@ for _, sCommandName in ipairs(tCommands) do
end
end
end
--- A table containing asynchronous wrappers for all commands.
--
-- As with @{commands.execAsync}, this returns the "task id" of the enqueued
-- command.
-- @see execAsync
-- @usage Asynchronously sets the block above the computer to stone.
--
-- commands.async.setblock("~", "~1", "~", "minecraft:stone")
env.async = tAsync

View File

@@ -75,7 +75,10 @@ handleMetatable = {
if not handle.read then return nil, "file is not readable" end
local args = table.pack(...)
return function() return checkResult(self, self:read(table.unpack(args, 1, args.n))) end
return function()
if self._closed then error("file is already closed", 2) end
return checkResult(self, self:read(table.unpack(args, 1, args.n)))
end
end,
read = function(self, ...)
@@ -259,12 +262,13 @@ end
-- instead. In this case, the handle is not used.
--
-- @tparam[opt] string filename The name of the file to extract lines from
-- @param ... The argument to pass to @{Handle:read} for each line.
-- @treturn function():string|nil The line iterator.
-- @throws If the file cannot be opened for reading
--
-- @see Handle:lines
-- @see io.input
function lines(filename)
function lines(filename, ...)
expect(1, filename, "string", "nil")
if filename then
local ok, err = open(filename, "rb")
@@ -273,9 +277,9 @@ function lines(filename)
-- We set this magic flag to mark this file as being opened by io.lines and so should be
-- closed automatically
ok._autoclose = true
return ok:lines()
return ok:lines(...)
else
return currentInput:lines()
return currentInput:lines(...)
end
end
@@ -313,7 +317,7 @@ end
-- @throws If the provided filename cannot be opened for writing.
function output(file)
if type_of(file) == "string" then
local res, err = open(file, "w")
local res, err = open(file, "wb")
if not res then error(err, 2) end
currentOutput = res
elseif type_of(file) == "table" and getmetatable(file) == handleMetatable then

View File

@@ -64,24 +64,33 @@ function isPresent(name)
return false
end
--- Get the type of the peripheral with the given name.
--- Get the type of a wrapped peripheral, or a peripheral with the given name.
--
-- @tparam string name The name of the peripheral to find.
-- @tparam string|table peripheral The name of the peripheral to find, or a
-- wrapped peripheral instance.
-- @treturn string|nil The peripheral's type, or `nil` if it is not present.
function getType(name)
expect(1, name, "string")
if native.isPresent(name) then
return native.getType(name)
end
for n = 1, #sides do
local side = sides[n]
if native.getType(side) == "modem" and not native.call(side, "isWireless") and
native.call(side, "isPresentRemote", name)
then
return native.call(side, "getTypeRemote", name)
function getType(peripheral)
expect(1, peripheral, "string", "table")
if type(peripheral) == "string" then -- Peripheral name passed
if native.isPresent(peripheral) then
return native.getType(peripheral)
end
for n = 1, #sides do
local side = sides[n]
if native.getType(side) == "modem" and not native.call(side, "isWireless") and
native.call(side, "isPresentRemote", peripheral)
then
return native.call(side, "getTypeRemote", peripheral)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.type) ~= "string" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.type
end
return nil
end
--- Get all available methods for the peripheral with the given name.
@@ -105,6 +114,19 @@ function getMethods(name)
return nil
end
--- Get the name of a peripheral wrapped with @{peripheral.wrap}.
--
-- @tparam table peripheral The peripheral to get the name of.
-- @treturn string The name of the given peripheral.
function getName(peripheral)
expect(1, peripheral, "table")
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.name
end
--- Call a method on the peripheral with the given name.
--
-- @tparam string name The name of the peripheral to invoke the method on.
@@ -148,7 +170,11 @@ function wrap(name)
return nil
end
local result = {}
local result = setmetatable({}, {
__name = "peripheral",
name = name,
type = peripheral.getType(name),
})
for _, method in ipairs(methods) do
result[method] = function(...)
return peripheral.call(name, method, ...)

View File

@@ -29,7 +29,7 @@ local tReceivedMessageTimeouts = {}
local tHostnames = {}
--- Opens a modem with the given @{peripheral} name, allowing it to send and
--- receive messages over rednet.
-- receive messages over rednet.
--
-- This will open the modem on two channels: one which has the same
-- @{os.getComputerID|ID} as the computer, and another on
@@ -246,7 +246,7 @@ function host(sProtocol, sHostname)
end
--- Stop @{rednet.host|hosting} a specific protocol, meaning it will no longer
--- respond to @{rednet.lookup} requests.
-- respond to @{rednet.lookup} requests.
--
-- @tparam string sProtocol The protocol to unregister your self from.
function unhost(sProtocol)
@@ -334,7 +334,11 @@ end
local bRunning = false
--- @local
--- Listen for modem messages and converts them into rednet messages, which may
-- then be @{receive|received}.
--
-- This is automatically started in the background on computer startup, and
-- should not be called manually.
function run()
if bRunning then
error("rednet is already running", 2)

View File

@@ -205,9 +205,9 @@ function load(sPath)
end
for k, v in pairs(tFile) do
local ty_v = type(k)
local ty_v = type(v)
if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then
local opt = details[name]
local opt = details[k]
if not opt or not opt.type or ty_v == opt.type then
set_value(k, v)
end

View File

@@ -5,7 +5,12 @@
if not turtle then
error("Cannot load turtle API on computer", 2)
end
native = turtle.native or turtle --- @local
--- The builtin turtle API, without any generated helper functions.
--
-- Generally you should not need to use this table - it only exists for
-- backwards compatibility reasons.
native = turtle.native or turtle
local function addCraftMethod(object)
if peripheral.getType("left") == "workbench" then

View File

@@ -1,3 +1,36 @@
# New features in CC: Tweaked 1.89.2
* Fix dupe bug when killing an entity with a turtle.
# New features in CC: Tweaked 1.89.1
* Fix crashes when rendering monitors of varying sizes.
# New features in CC: Tweaked 1.89.0
* Compress monitor data, reducing network traffic by a significant amount.
* Allow limiting the bandwidth monitor updates use.
* Several optimisations to monitor rendering (@Lignum)
And several bug fixes:
* Fix settings.load failing on defined settings.
# New features in CC: Tweaked 1.88.0
* Computers and turtles now preserve their ID when broken.
* Add `peripheral.getName` - returns the name of a wrapped peripheral.
* Reduce network overhead of monitors and terminals.
* Add a TBO backend for monitors, with a significant performance boost.
* The Lua REPL warns when declaring locals (lupus590, exerro)
* Add config to allow using command computers in survival.
* Add fs.isDriveRoot - checks if a path is the root of a drive.
* `cc.pretty` can now display a function's arguments and where it was defined. The Lua REPL will show arguments by default.
* Move the shell's `require`/`package` implementation to a separate `cc.require` module.
And several bug fixes:
* Fix io.lines not accepting arguments.
* Fix settings.load using an unknown global (MCJack123).
# New features in CC: Tweaked 1.87.0
* Add documentation to many Lua functions. This is published online at https://tweaked.cc/.

View File

@@ -1,4 +1,3 @@
ComputerCraft was created by Daniel "dan200" Ratcliffe, with additional code by Aaron "Cloudy" Mills.
Thanks to nitrogenfingers, GopherATL and RamiLego for program contributions.
Thanks to Mojang, the Forge team, and the MCP team.

View File

@@ -3,6 +3,7 @@ The peripheral API is for interacting with external peripheral devices. Type "he
Functions in the peripheral API:
peripheral.getNames()
peripheral.isPresent( name )
peripheral.getName( peripheral )
peripheral.getType( name )
peripheral.getMethods( name )
peripheral.call( name, methodName, param1, param2, etc )

View File

@@ -1,31 +1,5 @@
New features in CC: Tweaked 1.87.0
New features in CC: Tweaked 1.89.2
* Add documentation to many Lua functions. This is published online at https://tweaked.cc/.
* Replace to pretty-printer in the Lua REPL. It now supports displaying functions and recursive tables. This printer is may be used within your own code through the `cc.pretty` module.
* Add `fs.getCapacity`. A complement to `fs.getFreeSpace`, this returns the capacity of the supplied drive.
* Add `fs.getAttributes`. This provides file size and type, as well as creation and modification time.
* Update Cobalt version. This backports several features from Lua 5.2 and 5.3:
- The `__len` metamethod may now be used by tables.
- Add `\z`, hexadecimal (`\x00`) and unicode (`\u0000`) string escape codes.
- Add `utf8` lib.
- Mirror Lua's behaviour of tail calls more closely. Native functions are no longer tail called, and tail calls are displayed in the stack trace.
- `table.unpack` now uses `__len` and `__index` metamethods.
- Parser errors now include the token where the error occured.
* Add `textutils.unserializeJSON`. This can be used to decode standard JSON and stringified-NBT.
* Switch the monitor renderer to use VBOs, providing a _significant_ performance boost in some cases. You can switch back to the display list renderer via the config.
* The `settings` API now allows "defining" settings. This allows settings to specify a default value and description.
* Enable the motd on non-pocket computers.
* Allow using the menu with the mouse in edit and paint (JakobDev).
* Add Danish and Korean translations (ChristianLW, mindy15963)
* Fire `mouse_up` events in the monitor program.
* Allow specifying a timeout to `websocket.receive`.
* Increase the maximimum limit for websocket messages.
* Optimise capacity checking of computer/disk folders.
And several bug fixes:
* Fix turtle texture being incorrectly oriented (magiczocker10).
* Prevent copying folders into themselves.
* Fix race condition within ID assignment.
* Normalise file paths within shell.setDir (JakobDev)
* Fix dupe bug when killing an entity with a turtle.
Type "help changelog" to see the full version history.

View File

@@ -19,8 +19,12 @@
-- local pretty = require "cc.pretty"
-- pretty.write(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world")))
local expect = require "cc.expect".expect
local type, getmetatable, setmetatable, colours, str_write = type, getmetatable, setmetatable, colours, write
local expect = require "cc.expect"
local expect, field = expect.expect, expect.field
local type, getmetatable, setmetatable, colours, str_write, tostring = type, getmetatable, setmetatable, colours, write, tostring
local debug_info = type(debug) == "table" and type(debug.getinfo) == "function" and debug.getinfo
local debug_local = type(debug) == "table" and type(debug.getlocal) == "function" and debug.getlocal
--- @{table.insert} alternative, but with the length stored inline.
local function append(out, value)
@@ -343,13 +347,38 @@ local function key_compare(a, b)
return false
end
local function pretty_impl(obj, tracking)
local function show_function(fn, options)
local info = debug_info and debug_info(fn, "Su")
-- Include function source position if available
local name
if options.function_source and info and info.short_src and info.linedefined and info.linedefined >= 1 then
name = "function<" .. info.short_src .. ":" .. info.linedefined .. ">"
else
name = tostring(fn)
end
-- Include arguments if a Lua function and if available. Lua will report "C"
-- functions as variadic.
if options.function_args and info and info.what == "Lua" and info.nparams and debug_local then
local args = {}
for i = 1, info.nparams do args[i] = debug_local(fn, i) or "?" end
if info.isvararg then args[#args + 1] = "..." end
name = name .. "(" .. table.concat(args, ", ") .. ")"
end
return name
end
local function pretty_impl(obj, options, tracking)
local obj_type = type(obj)
if obj_type == "string" then
local formatted = ("%q"):format(obj):gsub("\\\n", "\\n")
return text(formatted, colours.red)
elseif obj_type == "number" then
return text(tostring(obj), colours.magenta)
elseif obj_type == "function" then
return text(show_function(obj, options), colours.lightGrey)
elseif obj_type ~= "table" or tracking[obj] then
return text(tostring(obj), colours.lightGrey)
elseif getmetatable(obj) ~= nil and getmetatable(obj).__tostring then
@@ -371,20 +400,20 @@ local function pretty_impl(obj, tracking)
local v = obj[k]
local ty = type(k)
if ty == "number" and k % 1 == 0 and k >= 1 and k <= length then
append(doc, pretty_impl(v, tracking))
append(doc, pretty_impl(v, options, tracking))
elseif ty == "string" and not keywords[k] and k:match("^[%a_][%a%d_]*$") then
append(doc, text(k .. " = "))
append(doc, pretty_impl(v, tracking))
append(doc, pretty_impl(v, options, tracking))
else
append(doc, obracket)
append(doc, pretty_impl(k, tracking))
append(doc, pretty_impl(k, options, tracking))
append(doc, cbracket)
append(doc, pretty_impl(v, tracking))
append(doc, pretty_impl(v, options, tracking))
end
end
tracking[obj] = nil
return group(concat(obrace, nest(2, concat(table.unpack(doc, 1, n))), space_line, cbrace))
return group(concat(obrace, nest(2, concat(table.unpack(doc, 1, doc.n))), space_line, cbrace))
end
end
@@ -393,12 +422,24 @@ end
-- This can then be rendered with @{write} or @{print}.
--
-- @param obj The object to pretty-print.
-- @tparam[opt] { function_args = boolean, function_source = boolean } options
-- Controls how various properties are displayed.
-- - `function_args`: Show the arguments to a function if known (`false` by default).
-- - `function_source`: Show where the function was defined, instead of
-- `function: xxxxxxxx` (`false` by default).
-- @treturn Doc The object formatted as a document.
-- @usage Display a table on the screen
-- local pretty = require "cc.pretty"
-- pretty.print(pretty.pretty({ 1, 2, 3 }))
local function pretty(obj)
return pretty_impl(obj, {})
local function pretty(obj, options)
expect(2, options, "table", "nil")
options = options or {}
local actual_options = {
function_source = field(options, "function_source", "boolean", "nil") or false,
function_args = field(options, "function_args", "boolean", "nil") or false,
}
return pretty_impl(obj, actual_options, {})
end
return {

View File

@@ -0,0 +1,121 @@
--- This provides a pure Lua implementation of the builtin @{require} function
-- and @{package} library.
--
-- 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 when running programs yourself.
--
-- @module cc.require
-- @usage Construct the package and require function, and insert them into a
-- custom environment.
--
-- local env = setmetatable({}, { __index = _ENV })
-- local r = require "cc.require"
-- env.require, env.package = r.make(env, "/")
local expect = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")
local expect = expect.expect
local function preload(package)
return function(name)
if package.preload[name] then
return package.preload[name]
else
return nil, "no field package.preload['" .. name .. "']"
end
end
end
local function from_file(package, env, dir)
return function(name)
local fname = string.gsub(name, "%.", "/")
local sError = ""
for pattern in string.gmatch(package.path, "[^;]+") do
local sPath = string.gsub(pattern, "%?", fname)
if sPath:sub(1, 1) ~= "/" then
sPath = fs.combine(dir, sPath)
end
if fs.exists(sPath) and not fs.isDir(sPath) then
local fnFile, sError = loadfile(sPath, nil, env)
if fnFile then
return fnFile, sPath
else
return nil, sError
end
else
if #sError > 0 then
sError = sError .. "\n "
end
sError = sError .. "no file '" .. sPath .. "'"
end
end
return nil, sError
end
end
local function make_require(package)
local sentinel = {}
return function(name)
expect(1, name, "string")
if package.loaded[name] == sentinel then
error("loop or previous error loading module '" .. name .. "'", 0)
end
if package.loaded[name] then
return package.loaded[name]
end
local sError = "module '" .. name .. "' not found:"
for _, searcher in ipairs(package.loaders) do
local loader = table.pack(searcher(name))
if loader[1] then
package.loaded[name] = sentinel
local result = loader[1](name, table.unpack(loader, 2, loader.n))
if result == nil then result = true end
package.loaded[name] = result
return result
else
sError = sError .. "\n " .. loader[2]
end
end
error(sError, 2)
end
end
--- Build an implementation of Lua's @{package} library, and a @{require}
-- function to load modules within it.
--
-- @tparam table env The environment to load packages into.
-- @tparam string dir The directory that relative packages are loaded from.
-- @treturn function The new @{require} function.
-- @treturn table The new @{package} library.
local function make_package(env, dir)
expect(1, env, "table")
expect(2, dir, "string")
local package = {}
package.loaded = {
_G = _G,
bit32 = bit32,
coroutine = coroutine,
math = math,
package = package,
string = string,
table = table,
}
package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua"
if turtle then
package.path = package.path .. ";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua"
elseif commands then
package.path = package.path .. ";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua"
end
package.config = "/\n;\n?\n!\n-"
package.preload = {}
package.loaders = { preload(package), from_file(package, env, dir) }
return make_require(package), package
end
return { make = make_package }

View File

@@ -141,12 +141,12 @@ return {
program = program,
-- Re-export various other functions
help = wrap(help.completeTopic),
choice = wrap(completion.choice),
peripheral = wrap(completion.peripheral),
side = wrap(completion.side),
setting = wrap(completion.setting),
command = wrap(completion.command),
help = wrap(help.completeTopic), --- Wraps @{help.completeTopic} as a @{build} compatible function.
choice = wrap(completion.choice), --- Wraps @{cc.completion.choice} as a @{build} compatible function.
peripheral = wrap(completion.peripheral), --- Wraps @{cc.completion.peripheral} as a @{build} compatible function.
side = wrap(completion.side), --- Wraps @{cc.completion.side} as a @{build} compatible function.
setting = wrap(completion.setting), --- Wraps @{cc.completion.setting} as a @{build} compatible function.
command = wrap(completion.command), --- Wraps @{cc.completion.command} as a @{build} compatible function.
build = build,
}

View File

@@ -1,4 +1,3 @@
if not shell.openTab then
printError("Requires multishell")
return

View File

@@ -1,4 +1,3 @@
if not shell.openTab then
printError("Requires multishell")
return

View File

@@ -1,4 +1,3 @@
local tArgs = { ... }
if #tArgs > 2 then
print("Usage: alias <alias> <program>")

View File

@@ -1,4 +1,3 @@
local tApis = {}
for k, v in pairs(_G) do
if type(k) == "string" and type(v) == "table" and k ~= "_G" then

View File

@@ -1,4 +1,3 @@
local tArgs = { ... }
if #tArgs < 1 then
print("Usage: cd <path>")

View File

@@ -1,4 +1,3 @@
if not commands then
printError("Requires a Command Computer.")
return

View File

@@ -1,4 +1,3 @@
local tArgs = { ... }
if not commands then
printError("Requires a Command Computer.")

View File

@@ -1,4 +1,3 @@
local tArgs = { ... }
if #tArgs < 2 then
print("Usage: cp <source> <destination>")

View File

@@ -9,9 +9,18 @@ for i = 1, args.n do
local files = fs.find(shell.resolve(args[i]))
if #files > 0 then
for _, file in ipairs(files) do
local ok, err = pcall(fs.delete, file)
if not ok then
printError((err:gsub("^pcall: ", "")))
if fs.isReadOnly(file) then
printError("Cannot delete read-only file /" .. file)
elseif fs.isDriveRoot(file) then
printError("Cannot delete mount /" .. file)
if fs.isDir(file) then
print("To delete its contents run rm /" .. fs.combine(file, "*"))
end
else
local ok, err = pcall(fs.delete, file)
if not ok then
printError((err:gsub("^pcall: ", "")))
end
end
end
else

View File

@@ -1,4 +1,3 @@
-- Get arguments
local tArgs = { ... }
if #tArgs == 0 then

View File

@@ -1,4 +1,3 @@
local tBiomes = {
"in a forest",
"in a pine forest",

View File

@@ -1,4 +1,3 @@
-- Display the start screen
local w, h = term.getSize()

View File

@@ -1,4 +1,3 @@
local function printUsage()
print("Usages:")
print("gps host")

View File

@@ -1,4 +1,3 @@
local function printUsage()
print("Usages:")
print("pastebin put <filename>")

View File

@@ -1,4 +1,3 @@
local function printUsage()
print("Usage:")
print("wget <url> [filename]")

View File

@@ -1,4 +1,3 @@
local sDrive = nil
local tArgs = { ... }
if #tArgs > 0 then

View File

@@ -1,4 +1,3 @@
local function printUsage()
print("Usages:")
print("label get")

View File

@@ -1,4 +1,3 @@
local tArgs = { ... }
-- Get all the files in the directory

View File

@@ -1,4 +1,3 @@
local tArgs = { ... }
if #tArgs > 0 then
print("This is an interactive Lua prompt.")
@@ -67,6 +66,13 @@ while bRunning do
if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then
table.insert(tCommandHistory, s)
end
if settings.get("lua.warn_against_use_of_local") and s:match("^%s*local%s+") then
if term.isColour() then
term.setTextColour(colours.yellow)
end
print("To access local variables in later inputs, remove the local keyword.")
term.setTextColour(colours.white)
end
local nForcePrint = 0
local func, e = load(s, "=lua", "t", tEnv)
@@ -89,7 +95,10 @@ while bRunning do
local n = 1
while n < tResults.n or n <= nForcePrint do
local value = tResults[n + 1]
local ok, serialised = pcall(pretty.pretty, value)
local ok, serialised = pcall(pretty.pretty, value, {
function_args = settings.get("lua.function_args"),
function_source = settings.get("lua.function_source"),
})
if ok then
pretty.print(serialised)
else

View File

@@ -28,7 +28,7 @@ local monitor = peripheral.wrap(sName)
local previousTerm = term.redirect(monitor)
local co = coroutine.create(function()
shell.run(sProgram, table.unpack(tArgs, 3))
(shell.execute or shell.run)(sProgram, table.unpack(tArgs, 3))
end)
local function resume(...)

View File

@@ -1,4 +1,3 @@
local tArgs = { ... }
if #tArgs < 2 then
print("Usage: mv <source> <destination>")
@@ -8,12 +7,35 @@ end
local sSource = shell.resolve(tArgs[1])
local sDest = shell.resolve(tArgs[2])
local tFiles = fs.find(sSource)
local function sanity_checks(source, dest)
if fs.exists(dest) then
printError("Destination exists")
return false
elseif fs.isReadOnly(dest) then
printError("Destination is read-only")
return false
elseif fs.isDriveRoot(source) then
printError("Cannot move mount /" .. source)
return false
elseif fs.isReadOnly(source) then
printError("Cannot move read-only file /" .. source)
return false
end
return true
end
if #tFiles > 0 then
for _, sFile in ipairs(tFiles) do
if fs.isDir(sDest) then
fs.move(sFile, fs.combine(sDest, fs.getName(sFile)))
local dest = fs.combine(sDest, fs.getName(sFile))
if sanity_checks(sFile, dest) then
fs.move(sFile, dest)
end
elseif #tFiles == 1 then
fs.move(sFile, sDest)
if sanity_checks(sFile, sDest) then
fs.move(sFile, sDest)
end
else
printError("Cannot overwrite file multiple times")
return

View File

@@ -1,4 +1,3 @@
local bAll = false
local tArgs = { ... }
if #tArgs > 0 and tArgs[1] == "all" then

View File

@@ -1,4 +1,3 @@
local tArgs = { ... }
local function printUsage()

View File

@@ -1,4 +1,3 @@
-- Find modems
local tModems = {}
for _, sModem in ipairs(peripheral.getNames()) do

View File

@@ -1,4 +1,3 @@
local tArgs = { ... }
local function printUsage()

View File

@@ -10,6 +10,12 @@ local sDest = shell.resolve(tArgs[2])
if not fs.exists(sSource) then
printError("No matching files")
return
elseif fs.isDriveRoot(sSource) then
printError("Can't rename mounts")
return
elseif fs.isReadOnly(sSource) then
printError("Source is read-only")
return
elseif fs.exists(sDest) then
printError("Destination exists")
return

View File

@@ -11,6 +11,7 @@
-- @module[module] shell
local expect = dofile("rom/modules/main/cc/expect.lua").expect
local make_package = dofile("rom/modules/main/cc/require.lua").make
local multishell = multishell
local parentShell = shell
@@ -28,94 +29,10 @@ local tCompletionInfo = parentShell and parentShell.getCompletionInfo() or {}
local tProgramStack = {}
local shell = {} --- @export
local function createShellEnv(sDir)
local tEnv = {}
tEnv.shell = shell
tEnv.multishell = multishell
local package = {}
package.loaded = {
_G = _G,
bit32 = bit32,
coroutine = coroutine,
math = math,
package = package,
string = string,
table = table,
}
package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua"
if turtle then
package.path = package.path .. ";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua"
elseif command then
package.path = package.path .. ";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua"
end
package.config = "/\n;\n?\n!\n-"
package.preload = {}
package.loaders = {
function(name)
if package.preload[name] then
return package.preload[name]
else
return nil, "no field package.preload['" .. name .. "']"
end
end,
function(name)
local fname = string.gsub(name, "%.", "/")
local sError = ""
for pattern in string.gmatch(package.path, "[^;]+") do
local sPath = string.gsub(pattern, "%?", fname)
if sPath:sub(1, 1) ~= "/" then
sPath = fs.combine(sDir, sPath)
end
if fs.exists(sPath) and not fs.isDir(sPath) then
local fnFile, sError = loadfile(sPath, nil, tEnv)
if fnFile then
return fnFile, sPath
else
return nil, sError
end
else
if #sError > 0 then
sError = sError .. "\n "
end
sError = sError .. "no file '" .. sPath .. "'"
end
end
return nil, sError
end,
}
local sentinel = {}
local function require(name)
expect(1, name, "string")
if package.loaded[name] == sentinel then
error("loop or previous error loading module '" .. name .. "'", 0)
end
if package.loaded[name] then
return package.loaded[name]
end
local sError = "module '" .. name .. "' not found:"
for _, searcher in ipairs(package.loaders) do
local loader = table.pack(searcher(name))
if loader[1] then
package.loaded[name] = sentinel
local result = loader[1](name, table.unpack(loader, 2, loader.n))
if result == nil then result = true end
package.loaded[name] = result
return result
else
sError = sError .. "\n " .. loader[2]
end
end
error(sError, 2)
end
tEnv.package = package
tEnv.require = require
return tEnv
local function createShellEnv(dir)
local env = { shell = shell, multishell = multishell }
env.require, env.package = make_package(env, dir)
return env
end
-- Colours
@@ -130,8 +47,25 @@ else
bgColour = colours.black
end
local function run(_sCommand, ...)
local sPath = shell.resolveProgram(_sCommand)
--- Run a program with the supplied arguments.
--
-- Unlike @{shell.run}, each argument is passed to the program verbatim. While
-- `shell.run("echo", "b c")` runs `echo` with `b` and `c`,
-- `shell.execute("echo", "b c")` runs `echo` with a single argument `b c`.
--
-- @tparam string command The program to execute.
-- @tparam string ... Arguments to this program.
-- @treturn boolean Whether the program exited successfully.
-- @usage Run `paint my-image` from within your program:
--
-- shell.execute("paint", "my-image")
function shell.execute(command, ...)
expect(1, command, "string")
for i = 1, select('#', ...) do
expect(i + 1, select(i, ...), "string")
end
local sPath = shell.resolveProgram(command)
if sPath ~= nil then
tProgramStack[#tProgramStack + 1] = sPath
if multishell then
@@ -144,7 +78,7 @@ local function run(_sCommand, ...)
local sDir = fs.getDir(sPath)
local env = createShellEnv(sDir)
env.arg = { [0] = _sCommand, ... }
env.arg = { [0] = command, ... }
local result = os.run(env, sPath, ...)
tProgramStack[#tProgramStack] = nil
@@ -196,11 +130,12 @@ end
-- @usage Run `paint my-image` from within your program:
--
-- shell.run("paint", "my-image")
-- @see shell.execute Run a program directly without parsing the arguments.
function shell.run(...)
local tWords = tokenise(...)
local sCommand = tWords[1]
if sCommand then
return run(sCommand, table.unpack(tWords, 2))
return shell.execute(sCommand, table.unpack(tWords, 2))
end
return false
end

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