mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-22 01:17:38 +00:00
Add redstone relay block (#2002)
- Move redstone methods out of the IAPIEnvironment, and into a new RedstoneAccess. We similarly move the implementation from Environment into a new RedstoneState class. The interface is possibly a little redundant (interfaces with a single implementation are always a little suspect), but it's nice to keep the consumer/producer interfaces separate. - Abstract most redstone API methods into a separate shared class, that can be used by both the rs API and the new redstone relay. - Add the new redstone relay block. The docs are probably a little lacking here, but I really struggled to write anything which wasn't just "look, it's the same as the redstone API".
This commit is contained in:
@@ -43,18 +43,6 @@ public interface IAPIEnvironment {
|
||||
|
||||
void queueEvent(String event, @Nullable Object... args);
|
||||
|
||||
void setOutput(ComputerSide side, int output);
|
||||
|
||||
int getOutput(ComputerSide side);
|
||||
|
||||
int getInput(ComputerSide side);
|
||||
|
||||
void setBundledOutput(ComputerSide side, int output);
|
||||
|
||||
int getBundledOutput(ComputerSide side);
|
||||
|
||||
int getBundledInput(ComputerSide side);
|
||||
|
||||
void setPeripheralChangeListener(@Nullable IPeripheralChangeListener listener);
|
||||
|
||||
@Nullable
|
||||
|
@@ -5,9 +5,9 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.redstone.RedstoneAccess;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -53,11 +53,9 @@ import java.util.List;
|
||||
* the Minecraft wiki."
|
||||
* @cc.module redstone
|
||||
*/
|
||||
public class RedstoneAPI implements ILuaAPI {
|
||||
private final IAPIEnvironment environment;
|
||||
|
||||
public RedstoneAPI(IAPIEnvironment environment) {
|
||||
this.environment = environment;
|
||||
public class RedstoneAPI extends RedstoneMethods implements ILuaAPI {
|
||||
public RedstoneAPI(RedstoneAccess environment) {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,131 +74,4 @@ public class RedstoneAPI implements ILuaAPI {
|
||||
public final List<String> getSides() {
|
||||
return ComputerSide.NAMES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the redstone signal of a specific side on or off.
|
||||
*
|
||||
* @param side The side to set.
|
||||
* @param on Whether the redstone signal should be on or off. When on, a signal strength of 15 is emitted.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void setOutput(ComputerSide side, boolean on) {
|
||||
environment.setOutput(side, on ? 15 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current redstone output of a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return Whether the redstone output is on or off.
|
||||
* @see #setOutput
|
||||
*/
|
||||
@LuaFunction
|
||||
public final boolean getOutput(ComputerSide side) {
|
||||
return environment.getOutput(side) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current redstone input of a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return Whether the redstone input is on or off.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final boolean getInput(ComputerSide side) {
|
||||
return environment.getInput(side) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the redstone signal strength for a specific side.
|
||||
*
|
||||
* @param side The side to set.
|
||||
* @param value The signal strength between 0 and 15.
|
||||
* @throws LuaException If {@code value} is not between 0 and 15.
|
||||
* @cc.since 1.51
|
||||
*/
|
||||
@LuaFunction({ "setAnalogOutput", "setAnalogueOutput" })
|
||||
public final void setAnalogOutput(ComputerSide side, int value) throws LuaException {
|
||||
if (value < 0 || value > 15) throw new LuaException("Expected number in range 0-15");
|
||||
environment.setOutput(side, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redstone output signal strength for a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The output signal strength, between 0 and 15.
|
||||
* @cc.since 1.51
|
||||
* @see #setAnalogOutput
|
||||
*/
|
||||
@LuaFunction({ "getAnalogOutput", "getAnalogueOutput" })
|
||||
public final int getAnalogOutput(ComputerSide side) {
|
||||
return environment.getOutput(side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redstone input signal strength for a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The input signal strength, between 0 and 15.
|
||||
* @cc.since 1.51
|
||||
*/
|
||||
@LuaFunction({ "getAnalogInput", "getAnalogueInput" })
|
||||
public final int getAnalogInput(ComputerSide side) {
|
||||
return environment.getInput(side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bundled cable output for a specific side.
|
||||
*
|
||||
* @param side The side to set.
|
||||
* @param output The colour bitmask to set.
|
||||
* @cc.see colors.subtract For removing a colour from the bitmask.
|
||||
* @cc.see colors.combine For adding a color to the bitmask.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void setBundledOutput(ComputerSide side, int output) {
|
||||
environment.setBundledOutput(side, output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bundled cable output for a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The bundle cable's output.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final int getBundledOutput(ComputerSide side) {
|
||||
return environment.getBundledOutput(side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bundled cable input for a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The bundle cable's input.
|
||||
* @see #testBundledInput To determine if a specific colour is set.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final int getBundledInput(ComputerSide side) {
|
||||
return environment.getBundledInput(side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a specific combination of colours are on for the given side.
|
||||
*
|
||||
* @param side The side to test.
|
||||
* @param mask The mask to test.
|
||||
* @return If the colours are on.
|
||||
* @cc.usage Check if [`colors.white`] and [`colors.black`] are on above the computer.
|
||||
* <pre>{@code
|
||||
* print(redstone.testBundledInput("top", colors.combine(colors.white, colors.black)))
|
||||
* }</pre>
|
||||
* @see #getBundledInput
|
||||
*/
|
||||
@LuaFunction
|
||||
public final boolean testBundledInput(ComputerSide side, int mask) {
|
||||
var input = environment.getBundledInput(side);
|
||||
return (input & mask) == mask;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,147 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.redstone.RedstoneAccess;
|
||||
|
||||
/**
|
||||
* A base class for all blocks with redstone integration.
|
||||
*/
|
||||
public class RedstoneMethods {
|
||||
private final RedstoneAccess redstone;
|
||||
|
||||
public RedstoneMethods(RedstoneAccess redstone) {
|
||||
this.redstone = redstone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the redstone signal of a specific side on or off.
|
||||
*
|
||||
* @param side The side to set.
|
||||
* @param on Whether the redstone signal should be on or off. When on, a signal strength of 15 is emitted.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void setOutput(ComputerSide side, boolean on) {
|
||||
redstone.setOutput(side, on ? 15 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current redstone output of a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return Whether the redstone output is on or off.
|
||||
* @see #setOutput
|
||||
*/
|
||||
@LuaFunction
|
||||
public final boolean getOutput(ComputerSide side) {
|
||||
return redstone.getOutput(side) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current redstone input of a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return Whether the redstone input is on or off.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final boolean getInput(ComputerSide side) {
|
||||
return redstone.getInput(side) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the redstone signal strength for a specific side.
|
||||
*
|
||||
* @param side The side to set.
|
||||
* @param value The signal strength between 0 and 15.
|
||||
* @throws LuaException If {@code value} is not between 0 and 15.
|
||||
* @cc.since 1.51
|
||||
*/
|
||||
@LuaFunction({ "setAnalogOutput", "setAnalogueOutput" })
|
||||
public final void setAnalogOutput(ComputerSide side, int value) throws LuaException {
|
||||
if (value < 0 || value > 15) throw new LuaException("Expected number in range 0-15");
|
||||
redstone.setOutput(side, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redstone output signal strength for a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The output signal strength, between 0 and 15.
|
||||
* @cc.since 1.51
|
||||
* @see #setAnalogOutput
|
||||
*/
|
||||
@LuaFunction({ "getAnalogOutput", "getAnalogueOutput" })
|
||||
public final int getAnalogOutput(ComputerSide side) {
|
||||
return redstone.getOutput(side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redstone input signal strength for a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The input signal strength, between 0 and 15.
|
||||
* @cc.since 1.51
|
||||
*/
|
||||
@LuaFunction({ "getAnalogInput", "getAnalogueInput" })
|
||||
public final int getAnalogInput(ComputerSide side) {
|
||||
return redstone.getInput(side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bundled cable output for a specific side.
|
||||
*
|
||||
* @param side The side to set.
|
||||
* @param output The colour bitmask to set.
|
||||
* @cc.see colors.subtract For removing a colour from the bitmask.
|
||||
* @cc.see colors.combine For adding a color to the bitmask.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void setBundledOutput(ComputerSide side, int output) {
|
||||
redstone.setBundledOutput(side, output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bundled cable output for a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The bundle cable's output.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final int getBundledOutput(ComputerSide side) {
|
||||
return redstone.getBundledOutput(side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bundled cable input for a specific side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The bundle cable's input.
|
||||
* @see #testBundledInput To determine if a specific colour is set.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final int getBundledInput(ComputerSide side) {
|
||||
return redstone.getBundledInput(side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a specific combination of colours are on for the given side.
|
||||
*
|
||||
* @param side The side to test.
|
||||
* @param mask The mask to test.
|
||||
* @return If the colours are on.
|
||||
* @cc.usage Check if [`colors.white`] and [`colors.black`] are on above this block.
|
||||
* <pre>{@code
|
||||
* print(redstone.testBundledInput("top", colors.combine(colors.white, colors.black)))
|
||||
* }</pre>
|
||||
* @see #getBundledInput
|
||||
*/
|
||||
@LuaFunction
|
||||
public final boolean testBundledInput(ComputerSide side, int mask) {
|
||||
var input = redstone.getBundledInput(side);
|
||||
return (input & mask) == mask;
|
||||
}
|
||||
}
|
@@ -12,10 +12,10 @@ import dan200.computercraft.core.ComputerContext;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.computer.mainthread.MainThreadScheduler;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.redstone.RedstoneState;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
@@ -54,8 +54,7 @@ public class Computer {
|
||||
|
||||
// Additional state about the computer and its environment.
|
||||
private final Environment internalEnvironment;
|
||||
|
||||
private final AtomicInteger externalOutputChanges = new AtomicInteger();
|
||||
private final RedstoneState redstone = new RedstoneState();
|
||||
|
||||
private boolean startRequested;
|
||||
private int ticksSinceStart = -1;
|
||||
@@ -87,6 +86,10 @@ public class Computer {
|
||||
return internalEnvironment;
|
||||
}
|
||||
|
||||
public RedstoneState getRedstone() {
|
||||
return redstone;
|
||||
}
|
||||
|
||||
public IAPIEnvironment getAPIEnvironment() {
|
||||
return internalEnvironment;
|
||||
}
|
||||
@@ -157,10 +160,8 @@ public class Computer {
|
||||
executor.tick();
|
||||
|
||||
// Update the environment's internal state.
|
||||
if (redstone.pollInputChanged()) queueEvent("redstone", null);
|
||||
internalEnvironment.tick();
|
||||
|
||||
// Propagate the environment's output to the world.
|
||||
externalOutputChanges.accumulateAndGet(internalEnvironment.updateOutput(), (x, y) -> x | y);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,8 +169,8 @@ public class Computer {
|
||||
*
|
||||
* @return What sides on the computer have changed.
|
||||
*/
|
||||
public int pollAndResetChanges() {
|
||||
return externalOutputChanges.getAndSet(0);
|
||||
public int pollRedstoneChanges() {
|
||||
return redstone.updateOutput();
|
||||
}
|
||||
|
||||
public boolean isBlinking() {
|
||||
|
@@ -153,11 +153,11 @@ final class ComputerExecutor implements ComputerScheduler.Worker {
|
||||
luaMethods = context.luaMethods();
|
||||
executor = context.computerScheduler().createExecutor(this, metrics);
|
||||
|
||||
var environment = computer.getEnvironment();
|
||||
var environment = computer.getAPIEnvironment();
|
||||
|
||||
// Add all default APIs to the loaded list.
|
||||
addApi(new TermAPI(environment));
|
||||
addApi(new RedstoneAPI(environment));
|
||||
addApi(new RedstoneAPI(computer.getRedstone()));
|
||||
addApi(new FSAPI(environment));
|
||||
addApi(new PeripheralAPI(environment, context.peripheralMethods()));
|
||||
addApi(new OSAPI(environment));
|
||||
@@ -434,14 +434,13 @@ final class ComputerExecutor implements ComputerScheduler.Worker {
|
||||
// Shutdown our APIs
|
||||
for (var api : apis) api.shutdown();
|
||||
computer.getEnvironment().reset();
|
||||
computer.getRedstone().clearOutput();
|
||||
|
||||
// Unload filesystem
|
||||
if (fileSystem != null) {
|
||||
fileSystem.close();
|
||||
fileSystem = null;
|
||||
}
|
||||
|
||||
computer.getEnvironment().resetOutput();
|
||||
} finally {
|
||||
isOnLock.unlock();
|
||||
}
|
||||
|
@@ -16,24 +16,12 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Represents the "environment" that a {@link Computer} exists in.
|
||||
* <p>
|
||||
* 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>
|
||||
* This handles storing and updating of peripherals and timers.
|
||||
*
|
||||
* <h1>Peripheral</h1>
|
||||
* We also keep track of peripherals. These are read on both threads, and only written on the main thread.
|
||||
@@ -43,17 +31,6 @@ public final class Environment implements IAPIEnvironment {
|
||||
private final ComputerEnvironment environment;
|
||||
private final MetricsObserver metrics;
|
||||
|
||||
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 @Nullable IPeripheralChangeListener peripheralListener = null;
|
||||
|
||||
@@ -111,76 +88,6 @@ public final class Environment implements IAPIEnvironment {
|
||||
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) {
|
||||
var 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) {
|
||||
var 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) {
|
||||
var index = side.ordinal();
|
||||
if (input[index] != level) {
|
||||
input[index] = level;
|
||||
inputChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBundledRedstoneInput(ComputerSide side, int combination) {
|
||||
var 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.
|
||||
*
|
||||
@@ -197,11 +104,6 @@ public final class Environment implements IAPIEnvironment {
|
||||
* 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();
|
||||
@@ -218,45 +120,6 @@ public final class Environment implements IAPIEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the main thread to propagate the internal outputs to the external ones.
|
||||
*
|
||||
* @return If the outputs have changed.
|
||||
*/
|
||||
int updateOutput() {
|
||||
// Mark output as changed if the internal redstone has changed
|
||||
synchronized (internalOutput) {
|
||||
if (!internalOutputChanged) return 0;
|
||||
|
||||
var changed = 0;
|
||||
|
||||
for (var i = 0; i < ComputerSide.COUNT; i++) {
|
||||
if (externalOutput[i] != internalOutput[i]) {
|
||||
externalOutput[i] = internalOutput[i];
|
||||
changed |= 1 << i;
|
||||
}
|
||||
|
||||
if (externalBundledOutput[i] != internalBundledOutput[i]) {
|
||||
externalBundledOutput[i] = internalBundledOutput[i];
|
||||
changed |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
internalOutputChanged = false;
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
||||
void resetOutput() {
|
||||
// Reset redstone output
|
||||
synchronized (internalOutput) {
|
||||
Arrays.fill(internalOutput, 0);
|
||||
Arrays.fill(internalBundledOutput, 0);
|
||||
internalOutputChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IPeripheral getPeripheral(ComputerSide side) {
|
||||
|
@@ -0,0 +1,63 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
package dan200.computercraft.core.redstone;
|
||||
|
||||
import dan200.computercraft.core.apis.RedstoneMethods;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
|
||||
/**
|
||||
* Common interface between blocks which provide and consume a redstone signal.
|
||||
*
|
||||
* @see RedstoneMethods Lua-facing methods wrapping this interface.
|
||||
* @see RedstoneState A concrete implementation of this class.
|
||||
*/
|
||||
public interface RedstoneAccess {
|
||||
/**
|
||||
* Set the redstone output on a given side.
|
||||
*
|
||||
* @param side The side to set.
|
||||
* @param output The output level, between 0 and 15.
|
||||
*/
|
||||
void setOutput(ComputerSide side, int output);
|
||||
|
||||
/**
|
||||
* Get the redstone output on a given side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The output level, between 0 and 15.
|
||||
*/
|
||||
int getOutput(ComputerSide side);
|
||||
|
||||
/**
|
||||
* Get the redstone input on a given side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The input level, between 0 and 15.
|
||||
*/
|
||||
int getInput(ComputerSide side);
|
||||
|
||||
/**
|
||||
* Set the bundled redstone output on a given side.
|
||||
*
|
||||
* @param side The side to set.
|
||||
* @param output The output state, as a 16-bit bitmask.
|
||||
*/
|
||||
void setBundledOutput(ComputerSide side, int output);
|
||||
|
||||
/**
|
||||
* Get the bundled redstone output on a given side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The output state, as a 16-bit bitmask.
|
||||
*/
|
||||
int getBundledOutput(ComputerSide side);
|
||||
|
||||
/**
|
||||
* Set the bundled redstone input on a given side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The input state, as a 16-bit bitmask.
|
||||
*/
|
||||
int getBundledInput(ComputerSide side);
|
||||
}
|
@@ -0,0 +1,248 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
package dan200.computercraft.core.redstone;
|
||||
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Manages the state of redstone inputs and ouputs on a computer (or other redstone emitting block).
|
||||
* <p>
|
||||
* As computers execute on a separate thread to the main Minecraft world, computers cannot immediately read or write
|
||||
* redstone values. Instead, we maintain a copy of the block's redstone inputs and outputs, and sync that with the
|
||||
* Minecraft world when needed.
|
||||
*
|
||||
* <h2>Input</h2>
|
||||
* Redstone inputs should be propagated immediately to the internal state of the computer. Computers (and other redstone
|
||||
* blocks) listen for block updates, fetch their neighbour's redstone state, and then call
|
||||
* {@link #setInput(ComputerSide, int, int)}.
|
||||
* <p>
|
||||
* However, we do not want to immediately schedule a {@code "redstone"} event, as otherwise we could schedule many
|
||||
* events in a single tick. Instead, the next time the block is ticked, the consumer should call
|
||||
* {@link #pollInputChanged()} and queue an event if needed.
|
||||
*
|
||||
* <h2>Output</h2>
|
||||
* In order to reduce block updates, we maintain a separate "internal" and "external" output state. Whenever a computer
|
||||
* sets a redstone output, the "internal" state is updated, and a dirty flag is set. When the computer is ticked,
|
||||
* {@link #updateOutput()} should be called, to copy the internal state to the external state. This returns a bitmask
|
||||
* indicating which sides have changed. The external outputs may then be read with {@link #getExternalOutput(ComputerSide)}
|
||||
* and {@link #getExternalBundledOutput(ComputerSide)}.
|
||||
*/
|
||||
public final class RedstoneState implements RedstoneAccess {
|
||||
private final @Nullable Runnable onOutputChanged;
|
||||
|
||||
private final ReentrantLock outputLock = new ReentrantLock();
|
||||
private @GuardedBy("outputLock") boolean internalOutputChanged = false;
|
||||
private final @GuardedBy("outputLock") int[] internalOutput = new int[ComputerSide.COUNT];
|
||||
private final @GuardedBy("outputLock") int[] internalBundledOutput = new int[ComputerSide.COUNT];
|
||||
|
||||
private final int[] externalOutput = new int[ComputerSide.COUNT];
|
||||
private final int[] externalBundledOutput = new int[ComputerSide.COUNT];
|
||||
|
||||
private final ReentrantLock inputLock = new ReentrantLock();
|
||||
private boolean inputChanged = false;
|
||||
private final @GuardedBy("inputLock") int[] input = new int[ComputerSide.COUNT];
|
||||
private final @GuardedBy("inputLock") int[] bundledInput = new int[ComputerSide.COUNT];
|
||||
|
||||
public RedstoneState() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link RedstoneState}, with a callback function to invoke when the <em>internal</em> output has
|
||||
* changed. This function is called from the computer thread.
|
||||
*
|
||||
* @param outputChanged The function to invoke when output has changed.
|
||||
*/
|
||||
public RedstoneState(@Nullable Runnable outputChanged) {
|
||||
this.onOutputChanged = outputChanged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInput(ComputerSide side) {
|
||||
inputLock.lock();
|
||||
try {
|
||||
return input[side.ordinal()];
|
||||
} finally {
|
||||
inputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBundledInput(ComputerSide side) {
|
||||
inputLock.lock();
|
||||
try {
|
||||
return bundledInput[side.ordinal()];
|
||||
} finally {
|
||||
inputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutput(ComputerSide side, int output) {
|
||||
var index = side.ordinal();
|
||||
|
||||
outputLock.lock();
|
||||
try {
|
||||
if (internalOutput[index] == output) return;
|
||||
internalOutput[index] = output;
|
||||
setOutputChanged();
|
||||
} finally {
|
||||
outputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutput(ComputerSide side) {
|
||||
outputLock.lock();
|
||||
try {
|
||||
return internalOutput[side.ordinal()];
|
||||
} finally {
|
||||
outputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBundledOutput(ComputerSide side, int output) {
|
||||
var index = side.ordinal();
|
||||
outputLock.lock();
|
||||
try {
|
||||
if (internalBundledOutput[index] == output) return;
|
||||
internalBundledOutput[index] = output;
|
||||
setOutputChanged();
|
||||
} finally {
|
||||
outputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBundledOutput(ComputerSide side) {
|
||||
outputLock.lock();
|
||||
try {
|
||||
return internalBundledOutput[side.ordinal()];
|
||||
} finally {
|
||||
outputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("outputLock")
|
||||
private void setOutputChanged() {
|
||||
if (internalOutputChanged) return;
|
||||
internalOutputChanged = true;
|
||||
if (onOutputChanged != null) onOutputChanged.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate redstone changes from the computer to the outside world. The effective outputs can be acquired with
|
||||
* {@link #getExternalOutput(ComputerSide)} and {@link #getExternalBundledOutput(ComputerSide)}.
|
||||
*
|
||||
* @return A bitmask indicating which sides have changed (indexed via {@link ComputerSide#ordinal()}).
|
||||
*/
|
||||
public int updateOutput() {
|
||||
outputLock.lock();
|
||||
try {
|
||||
if (!internalOutputChanged) return 0;
|
||||
|
||||
var changed = 0;
|
||||
|
||||
for (var i = 0; i < ComputerSide.COUNT; i++) {
|
||||
if (externalOutput[i] != internalOutput[i]) {
|
||||
externalOutput[i] = internalOutput[i];
|
||||
changed |= 1 << i;
|
||||
}
|
||||
|
||||
if (externalBundledOutput[i] != internalBundledOutput[i]) {
|
||||
externalBundledOutput[i] = internalBundledOutput[i];
|
||||
changed |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
internalOutputChanged = false;
|
||||
|
||||
return changed;
|
||||
} finally {
|
||||
outputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redstone output for a given side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The effective redstone output.
|
||||
*/
|
||||
public int getExternalOutput(ComputerSide side) {
|
||||
return externalOutput[side.ordinal()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bundled redstone output for a given side.
|
||||
*
|
||||
* @param side The side to get.
|
||||
* @return The effective bundled redstone output.
|
||||
*/
|
||||
public int getExternalBundledOutput(ComputerSide side) {
|
||||
return externalBundledOutput[side.ordinal()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset any redstone output set by the computer.
|
||||
*/
|
||||
public void clearOutput() {
|
||||
outputLock.lock();
|
||||
try {
|
||||
Arrays.fill(internalOutput, 0);
|
||||
Arrays.fill(internalBundledOutput, 0);
|
||||
internalOutputChanged = true;
|
||||
} finally {
|
||||
outputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the redstone input for a given side.
|
||||
*
|
||||
* @param side The side to update.
|
||||
* @param level The redstone level.
|
||||
* @param bundledState The bundled redstone state.
|
||||
* @return Whether the input has changed.
|
||||
*/
|
||||
public boolean setInput(ComputerSide side, int level, int bundledState) {
|
||||
var index = side.ordinal();
|
||||
inputLock.lock();
|
||||
try {
|
||||
var changed = false;
|
||||
if (input[index] != level) {
|
||||
input[index] = level;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (bundledInput[index] != bundledState) {
|
||||
bundledInput[index] = bundledState;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
inputChanged |= changed;
|
||||
return changed;
|
||||
} finally {
|
||||
inputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether any redstone inputs set by {@link #setInput(ComputerSide, int, int)} have changed since the last
|
||||
* call to this function.
|
||||
*
|
||||
* @return Whether any redstone inputs has changed.
|
||||
*/
|
||||
public boolean pollInputChanged() {
|
||||
var changed = inputChanged;
|
||||
inputChanged = false;
|
||||
return changed;
|
||||
}
|
||||
}
|
@@ -63,34 +63,6 @@ public abstract class BasicApiEnvironment implements IAPIEnvironment {
|
||||
public void reboot() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutput(ComputerSide side, int output) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutput(ComputerSide side) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInput(ComputerSide side) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBundledOutput(ComputerSide side, int output) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBundledOutput(ComputerSide side) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBundledInput(ComputerSide side) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPeripheralChangeListener(@Nullable IPeripheralChangeListener listener) {
|
||||
}
|
||||
|
Reference in New Issue
Block a user