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

Compare commits

...

14 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
36 changed files with 763 additions and 351 deletions

View File

@@ -1,53 +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
- name: Upload Coverage
run: bash <(curl -s https://codecov.io/bash)
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

@@ -52,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 {
@@ -78,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'
@@ -144,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**'
@@ -265,7 +260,7 @@ license {
mapping("java", "SLASHSTAR_STYLE")
strictCheck true
ext.year = Calendar.getInstance().get(Calendar.YEAR)
ext.year = 2020
}
[licenseMain, licenseFormatMain].forEach {

View File

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

View File

@@ -138,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;
@@ -537,7 +538,7 @@ public class ComputerCraft
}
catch( IOException e )
{
if( zipFile != null ) IoUtil.closeQuietly( zipFile );
IoUtil.closeQuietly( zipFile );
}
}
}

View File

@@ -13,7 +13,6 @@ 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.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
@@ -27,12 +26,8 @@ class MonitorTextureBufferShader
{
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer( 16 );
private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 );
private static int uniformMv;
private static int uniformP;
private static int uniformFont;
private static int uniformWidth;
private static int uniformHeight;
@@ -45,16 +40,6 @@ class MonitorTextureBufferShader
static void setupUniform( int width, int height, Palette palette, boolean greyscale )
{
MATRIX_BUFFER.rewind();
GL11.glGetFloat( GL11.GL_MODELVIEW_MATRIX, MATRIX_BUFFER );
MATRIX_BUFFER.rewind();
OpenGlHelper.glUniformMatrix4( uniformMv, false, MATRIX_BUFFER );
MATRIX_BUFFER.rewind();
GL11.glGetFloat( GL11.GL_PROJECTION_MATRIX, MATRIX_BUFFER );
MATRIX_BUFFER.rewind();
OpenGlHelper.glUniformMatrix4( uniformP, false, MATRIX_BUFFER );
OpenGlHelper.glUniform1i( uniformWidth, width );
OpenGlHelper.glUniform1i( uniformHeight, height );
@@ -123,9 +108,6 @@ class MonitorTextureBufferShader
if( !ok ) return false;
uniformMv = getUniformLocation( program, "u_mv" );
uniformP = getUniformLocation( program, "u_p" );
uniformFont = getUniformLocation( program, "u_font" );
uniformWidth = getUniformLocation( program, "u_width" );
uniformHeight = getUniformLocation( program, "u_height" );

View File

@@ -35,6 +35,7 @@ import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_
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 )
@@ -162,7 +163,14 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw )
{
ByteBuffer monitorBuffer = GLAllocation.createDirectByteBuffer( width * height * 3 );
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 );

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

@@ -72,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;
@@ -276,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
);
}
@@ -474,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

@@ -6,9 +6,7 @@
package dan200.computercraft.shared.common;
import dan200.computercraft.core.terminal.Terminal;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
import dan200.computercraft.shared.network.client.TerminalState;
public class ClientTerminal implements ITerminal
{
@@ -48,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.read( new PacketBuffer( Unpooled.wrappedBuffer( terminal.getByteArray( "term_contents" ) ) ) );
resizeTerminal( state.width, state.height );
state.apply( m_terminal );
}
else
{

View File

@@ -5,12 +5,8 @@
*/
package dan200.computercraft.shared.common;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.terminal.Terminal;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
import dan200.computercraft.shared.network.client.TerminalState;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -73,8 +69,6 @@ public class ServerTerminal implements ITerminal
return m_terminalChangedLastFrame;
}
// ITerminal implementation
@Override
public Terminal getTerminal()
{
@@ -87,29 +81,8 @@ public class ServerTerminal implements ITerminal
return m_colour;
}
public void writeDescription( NBTTagCompound nbt )
public TerminalState write()
{
nbt.setBoolean( "colour", m_colour );
if( m_terminal != null )
{
// We have a 10 byte header (2 integer positions, then blinking and current colours), followed by the
// contents and palette.
// Yes, this serialisation code is terrible, but we need to serialise to NBT in order to work with monitors
// (or rather tile entity serialisation).
final int length = 10 + (2 * m_terminal.getWidth() * m_terminal.getHeight()) + (16 * 3);
ByteBuf buffer = Unpooled.buffer( length );
m_terminal.write( new PacketBuffer( buffer ) );
if( buffer.writableBytes() != 0 )
{
ComputerCraft.log.warn( "Should have written {} bytes, but have {} ({} remaining).", length, buffer.writerIndex(), buffer.writableBytes() );
}
NBTTagCompound terminal = new NBTTagCompound();
terminal.setInteger( "term_width", m_terminal.getWidth() );
terminal.setInteger( "term_height", m_terminal.getHeight() );
terminal.setByteArray( "term_contents", buffer.array() );
nbt.setTag( "terminal", terminal );
}
return new TerminalState( m_colour, m_terminal );
}
}

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

@@ -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

@@ -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

@@ -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

@@ -191,6 +191,7 @@ 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

@@ -205,7 +205,7 @@ 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[k]
if not opt or not opt.type or ty_v == opt.type then

View File

@@ -1,3 +1,20 @@
# 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.

View File

@@ -1,17 +1,5 @@
New features in CC: Tweaked 1.88.0
New features in CC: Tweaked 1.89.2
* 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).
* Fix dupe bug when killing an entity with a turtle.
Type "help changelog" to see the full version history.

View File

@@ -35,6 +35,6 @@ void main() {
int bg = int(texelFetch(u_tbo, index + 2).r * 255.0);
vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT);
vec4 img = texture2D(u_font, (texture_corner(character) + pos) / 256.0);
vec4 img = texture(u_font, (texture_corner(character) + pos) / 256.0);
colour = vec4(mix(u_palette[bg], img.rgb * u_palette[fg], img.a * mult), 1.0);
}

View File

@@ -1,13 +1,10 @@
#version 140
uniform mat4 u_mv;
uniform mat4 u_p;
#version 130
in vec3 v_pos;
out vec2 f_pos;
void main() {
gl_Position = u_p * u_mv * vec4(v_pos.x, v_pos.y, 0, 1);
gl_Position = gl_ModelViewProjectionMatrix * vec4(v_pos.x, v_pos.y, 0, 1);
f_pos = v_pos.xy;
}

View File

@@ -0,0 +1,94 @@
/*
* 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.ComputerCraft;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import io.netty.buffer.Unpooled;
import net.minecraft.network.PacketBuffer;
import org.apache.logging.log4j.LogManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.RepeatedTest;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests {@link TerminalState} round tripping works as expected.
*/
public class TerminalStateTest
{
@BeforeAll
public static void before()
{
ComputerCraft.log = LogManager.getLogger( ComputerCraft.MOD_ID );
}
@RepeatedTest( 5 )
public void testCompressed()
{
Terminal terminal = randomTerminal();
PacketBuffer buffer = new PacketBuffer( Unpooled.directBuffer() );
new TerminalState( true, terminal, true ).write( buffer );
checkEqual( terminal, read( buffer ) );
assertEquals( 0, buffer.readableBytes() );
}
@RepeatedTest( 5 )
public void testUncompressed()
{
Terminal terminal = randomTerminal();
PacketBuffer buffer = new PacketBuffer( Unpooled.directBuffer() );
new TerminalState( true, terminal, false ).write( buffer );
checkEqual( terminal, read( buffer ) );
assertEquals( 0, buffer.readableBytes() );
}
private static Terminal randomTerminal()
{
Random random = new Random();
Terminal terminal = new Terminal( 10, 5 );
for( int y = 0; y < terminal.getHeight(); y++ )
{
TextBuffer buffer = terminal.getLine( y );
for( int x = 0; x < buffer.length(); x++ ) buffer.setChar( x, (char) (random.nextInt( 26 ) + 65) );
}
return terminal;
}
private static void checkEqual( Terminal expected, Terminal actual )
{
assertNotNull( expected, "Expected cannot be null" );
assertNotNull( actual, "Actual cannot be null" );
assertEquals( expected.getHeight(), actual.getHeight(), "Heights must match" );
assertEquals( expected.getWidth(), actual.getWidth(), "Widths must match" );
for( int y = 0; y < expected.getHeight(); y++ )
{
assertEquals( expected.getLine( y ).toString(), actual.getLine( y ).toString() );
}
}
private static Terminal read( PacketBuffer buffer )
{
TerminalState state = new TerminalState( buffer );
assertTrue( state.colour );
if( !state.hasTerminal() ) return null;
Terminal other = new Terminal( state.width, state.height );
state.apply( other );
return other;
}
}

View File

@@ -153,11 +153,50 @@ describe("The settings library", function()
expect.error(settings.load, 1):eq("bad argument #1 (expected string, got number)")
end)
local function setup_with(contents)
settings.clear()
local h = fs.open("/test-files/.settings", "w")
h.write(contents)
h.close()
return settings.load("/test-files/.settings")
end
local function setup(contents)
return setup_with(textutils.serialize(contents))
end
it("defaults to .settings", function()
local s = stub(fs, "open")
settings.load()
expect(s):called_with(".settings", "r")
end)
it("loads undefined settings", function()
expect(setup { ["test"] = 1 }):eq(true)
expect(settings.get("test")):eq(1)
end)
it("loads defined settings", function()
settings.define("test.defined", { type = "number" })
expect(setup { ["test.defined"] = 1 }):eq(true)
expect(settings.get("test.defined")):eq(1)
end)
it("skips defined settings with incorrect types", function()
settings.define("test.defined", { type = "number" })
expect(setup { ["test.defined"] = "abc" }):eq(true)
expect(settings.get("test.defined")):eq(nil)
end)
it("skips unserializable values", function()
expect(setup_with "{ test = function() end }"):eq(true)
expect(settings.get("test")):eq(nil)
end)
it("skips non-table files", function()
expect(setup "not a table"):eq(false)
end)
end)
describe("settings.save", function()