378 lines
9.8 KiB
Java
378 lines
9.8 KiB
Java
/*
|
|
* 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.core.computer;
|
|
|
|
import dan200.computercraft.api.lua.ILuaAPI;
|
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
|
import dan200.computercraft.api.peripheral.IWorkMonitor;
|
|
import dan200.computercraft.core.apis.IAPIEnvironment;
|
|
import dan200.computercraft.core.filesystem.FileSystem;
|
|
import dan200.computercraft.core.terminal.Terminal;
|
|
import dan200.computercraft.core.tracking.Tracking;
|
|
import dan200.computercraft.core.tracking.TrackingField;
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
|
|
import javax.annotation.Nonnull;
|
|
import java.util.Arrays;
|
|
import java.util.Iterator;
|
|
|
|
/**
|
|
* Represents the "environment" that a {@link Computer} exists in.
|
|
*
|
|
* This handles storing and updating of peripherals and redstone.
|
|
*
|
|
* <h1>Redstone</h1>
|
|
* We holds three kinds of arrays for redstone, in normal and bundled versions:
|
|
* <ul>
|
|
* <li>{@link #internalOutput} is the redstone output which the computer has currently set. This is read on both
|
|
* threads, and written on the computer thread.</li>
|
|
* <li>{@link #externalOutput} is the redstone output currently propagated to the world. This is only read and written
|
|
* on the main thread.</li>
|
|
* <li>{@link #input} is the redstone input from external sources. This is read on both threads, and written on the main
|
|
* thread.</li>
|
|
* </ul>
|
|
*
|
|
* <h1>Peripheral</h1>
|
|
* We also keep track of peripherals. These are read on both threads, and only written on the main thread.
|
|
*/
|
|
public final class Environment implements IAPIEnvironment
|
|
{
|
|
private final Computer computer;
|
|
|
|
private boolean internalOutputChanged = false;
|
|
private final int[] internalOutput = new int[ComputerSide.COUNT];
|
|
private final int[] internalBundledOutput = new int[ComputerSide.COUNT];
|
|
|
|
private final int[] externalOutput = new int[ComputerSide.COUNT];
|
|
private final int[] externalBundledOutput = new int[ComputerSide.COUNT];
|
|
|
|
private boolean inputChanged = false;
|
|
private final int[] input = new int[ComputerSide.COUNT];
|
|
private final int[] bundledInput = new int[ComputerSide.COUNT];
|
|
|
|
private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT];
|
|
private IPeripheralChangeListener peripheralListener = null;
|
|
|
|
private final Int2ObjectMap<Timer> timers = new Int2ObjectOpenHashMap<>();
|
|
private int nextTimerToken = 0;
|
|
|
|
Environment( Computer computer )
|
|
{
|
|
this.computer = computer;
|
|
}
|
|
|
|
@Override
|
|
public int getComputerID()
|
|
{
|
|
return computer.assignID();
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public IComputerEnvironment getComputerEnvironment()
|
|
{
|
|
return computer.getComputerEnvironment();
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public IWorkMonitor getMainThreadMonitor()
|
|
{
|
|
return computer.getMainThreadMonitor();
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public Terminal getTerminal()
|
|
{
|
|
return computer.getTerminal();
|
|
}
|
|
|
|
@Override
|
|
public FileSystem getFileSystem()
|
|
{
|
|
return computer.getFileSystem();
|
|
}
|
|
|
|
@Override
|
|
public void shutdown()
|
|
{
|
|
computer.shutdown();
|
|
}
|
|
|
|
@Override
|
|
public void reboot()
|
|
{
|
|
computer.reboot();
|
|
}
|
|
|
|
@Override
|
|
public void queueEvent( String event, Object... args )
|
|
{
|
|
computer.queueEvent( event, args );
|
|
}
|
|
|
|
@Override
|
|
public int getInput( ComputerSide side )
|
|
{
|
|
return input[side.ordinal()];
|
|
}
|
|
|
|
@Override
|
|
public int getBundledInput( ComputerSide side )
|
|
{
|
|
return bundledInput[side.ordinal()];
|
|
}
|
|
|
|
@Override
|
|
public void setOutput( ComputerSide side, int output )
|
|
{
|
|
int index = side.ordinal();
|
|
synchronized( internalOutput )
|
|
{
|
|
if( internalOutput[index] != output )
|
|
{
|
|
internalOutput[index] = output;
|
|
internalOutputChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getOutput( ComputerSide side )
|
|
{
|
|
synchronized( internalOutput )
|
|
{
|
|
return computer.isOn() ? internalOutput[side.ordinal()] : 0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setBundledOutput( ComputerSide side, int output )
|
|
{
|
|
int index = side.ordinal();
|
|
synchronized( internalOutput )
|
|
{
|
|
if( internalBundledOutput[index] != output )
|
|
{
|
|
internalBundledOutput[index] = output;
|
|
internalOutputChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getBundledOutput( ComputerSide side )
|
|
{
|
|
synchronized( internalOutput )
|
|
{
|
|
return computer.isOn() ? internalBundledOutput[side.ordinal()] : 0;
|
|
}
|
|
}
|
|
|
|
public int getExternalRedstoneOutput( ComputerSide side )
|
|
{
|
|
return computer.isOn() ? externalOutput[side.ordinal()] : 0;
|
|
}
|
|
|
|
public int getExternalBundledRedstoneOutput( ComputerSide side )
|
|
{
|
|
return computer.isOn() ? externalBundledOutput[side.ordinal()] : 0;
|
|
}
|
|
|
|
public void setRedstoneInput( ComputerSide side, int level )
|
|
{
|
|
int index = side.ordinal();
|
|
if( input[index] != level )
|
|
{
|
|
input[index] = level;
|
|
inputChanged = true;
|
|
}
|
|
}
|
|
|
|
public void setBundledRedstoneInput( ComputerSide side, int combination )
|
|
{
|
|
int index = side.ordinal();
|
|
if( bundledInput[index] != combination )
|
|
{
|
|
bundledInput[index] = combination;
|
|
inputChanged = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the computer starts up or shuts down, to reset any internal state.
|
|
*
|
|
* @see ILuaAPI#startup()
|
|
* @see ILuaAPI#shutdown()
|
|
*/
|
|
void reset()
|
|
{
|
|
synchronized( timers )
|
|
{
|
|
timers.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called on the main thread to update the internal state of the computer.
|
|
*/
|
|
void tick()
|
|
{
|
|
if( inputChanged )
|
|
{
|
|
inputChanged = false;
|
|
queueEvent( "redstone" );
|
|
}
|
|
|
|
synchronized( timers )
|
|
{
|
|
// Countdown all of our active timers
|
|
Iterator<Int2ObjectMap.Entry<Timer>> it = timers.int2ObjectEntrySet().iterator();
|
|
while( it.hasNext() )
|
|
{
|
|
Int2ObjectMap.Entry<Timer> entry = it.next();
|
|
Timer timer = entry.getValue();
|
|
timer.ticksLeft--;
|
|
if( timer.ticksLeft <= 0 )
|
|
{
|
|
// Queue the "timer" event
|
|
queueEvent( TIMER_EVENT, entry.getIntKey() );
|
|
it.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called on the main thread to propagate the internal outputs to the external ones.
|
|
*
|
|
* @return If the outputs have changed.
|
|
*/
|
|
boolean updateOutput()
|
|
{
|
|
// Mark output as changed if the internal redstone has changed
|
|
synchronized( internalOutput )
|
|
{
|
|
if( !internalOutputChanged ) return false;
|
|
|
|
boolean changed = false;
|
|
|
|
for( int i = 0; i < ComputerSide.COUNT; i++ )
|
|
{
|
|
if( externalOutput[i] != internalOutput[i] )
|
|
{
|
|
externalOutput[i] = internalOutput[i];
|
|
changed = true;
|
|
}
|
|
|
|
if( externalBundledOutput[i] != internalBundledOutput[i] )
|
|
{
|
|
externalBundledOutput[i] = internalBundledOutput[i];
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
internalOutputChanged = false;
|
|
|
|
return changed;
|
|
}
|
|
}
|
|
|
|
void resetOutput()
|
|
{
|
|
// Reset redstone output
|
|
synchronized( internalOutput )
|
|
{
|
|
Arrays.fill( internalOutput, 0 );
|
|
Arrays.fill( internalBundledOutput, 0 );
|
|
internalOutputChanged = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public IPeripheral getPeripheral( ComputerSide side )
|
|
{
|
|
synchronized( peripherals )
|
|
{
|
|
return peripherals[side.ordinal()];
|
|
}
|
|
}
|
|
|
|
public void setPeripheral( ComputerSide side, IPeripheral peripheral )
|
|
{
|
|
synchronized( peripherals )
|
|
{
|
|
int index = side.ordinal();
|
|
IPeripheral existing = peripherals[index];
|
|
if( (existing == null && peripheral != null) ||
|
|
(existing != null && peripheral == null) ||
|
|
(existing != null && !existing.equals( peripheral )) )
|
|
{
|
|
peripherals[index] = peripheral;
|
|
if( peripheralListener != null ) peripheralListener.onPeripheralChanged( side, peripheral );
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setPeripheralChangeListener( IPeripheralChangeListener listener )
|
|
{
|
|
synchronized( peripherals )
|
|
{
|
|
peripheralListener = listener;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getLabel()
|
|
{
|
|
return computer.getLabel();
|
|
}
|
|
|
|
@Override
|
|
public void setLabel( String label )
|
|
{
|
|
computer.setLabel( label );
|
|
}
|
|
|
|
@Override
|
|
public int startTimer( long ticks )
|
|
{
|
|
synchronized( timers )
|
|
{
|
|
timers.put( nextTimerToken, new Timer( ticks ) );
|
|
return nextTimerToken++;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void cancelTimer( int id )
|
|
{
|
|
synchronized( timers )
|
|
{
|
|
timers.remove( id );
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addTrackingChange( @Nonnull TrackingField field, long change )
|
|
{
|
|
Tracking.addValue( computer, field, change );
|
|
}
|
|
|
|
private static class Timer
|
|
{
|
|
long ticksLeft;
|
|
|
|
Timer( long ticksLeft )
|
|
{
|
|
this.ticksLeft = ticksLeft;
|
|
}
|
|
}
|
|
}
|