CC-Tweaked/projects/core/src/main/java/dan200/computercraft/core/computer/Computer.java

191 lines
5.7 KiB
Java

// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.core.computer;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaTask;
import dan200.computercraft.api.peripheral.WorkMonitor;
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.terminal.Terminal;
import javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Represents a computer which may exist in-world or elsewhere.
* <p>
* Note, this class has several (read: far, far too many) responsibilities, so can get a little unwieldy at times.
*
* <ul>
* <li>Updates the {@link Environment}.</li>
* <li>Keeps track of whether the computer is on and blinking.</li>
* <li>Monitors whether the computer's visible state (redstone, on/off/blinking) has changed.</li>
* <li>Passes commands and events to the {@link ComputerExecutor}.</li>
* <li>Passes main thread tasks to the {@link MainThreadScheduler.Executor}.</li>
* </ul>
*/
public class Computer {
private static final int START_DELAY = 50;
// Various properties of the computer
private final int id;
private @Nullable String label = null;
// Read-only fields about the computer
private final GlobalEnvironment globalEnvironment;
private final Terminal terminal;
private final ComputerExecutor executor;
private final MainThreadScheduler.Executor serverExecutor;
/**
* An internal counter for {@link LuaTask} ids.
*
* @see ILuaContext#issueMainThreadTask(LuaTask)
* @see #getUniqueTaskId()
*/
private final AtomicLong lastTaskId = new AtomicLong();
// Additional state about the computer and its environment.
private final Environment internalEnvironment;
private final AtomicInteger externalOutputChanges = new AtomicInteger();
private boolean startRequested;
private int ticksSinceStart = -1;
public Computer(ComputerContext context, ComputerEnvironment environment, Terminal terminal, int id) {
if (id < 0) throw new IllegalStateException("Id has not been assigned");
this.id = id;
globalEnvironment = context.globalEnvironment();
this.terminal = terminal;
internalEnvironment = new Environment(this, environment);
executor = new ComputerExecutor(this, environment, context);
serverExecutor = context.mainThreadScheduler().createExecutor(environment.getMetrics());
}
GlobalEnvironment getGlobalEnvironment() {
return globalEnvironment;
}
FileSystem getFileSystem() {
return executor.getFileSystem();
}
Terminal getTerminal() {
return terminal;
}
public Environment getEnvironment() {
return internalEnvironment;
}
public IAPIEnvironment getAPIEnvironment() {
return internalEnvironment;
}
public boolean isOn() {
return executor.isOn();
}
public void turnOn() {
startRequested = true;
}
public void shutdown() {
executor.queueStop(false, false);
}
public void reboot() {
executor.queueStop(true, false);
}
public void unload() {
executor.queueStop(false, true);
}
public void queueEvent(String event, @Nullable Object[] args) {
executor.queueEvent(event, args);
}
/**
* Queue a task to be run on the main thread, using {@link MainThreadScheduler}.
*
* @param runnable The task to run
* @return If the task was successfully queued (namely, whether there is space on it).
*/
public boolean queueMainThread(Runnable runnable) {
return serverExecutor.enqueue(runnable);
}
public WorkMonitor getMainThreadMonitor() {
return serverExecutor;
}
public int getID() {
return id;
}
@Nullable
public String getLabel() {
return label;
}
public void setLabel(@Nullable String label) {
this.label = label;
}
public void tick() {
// We keep track of the number of ticks since the last start, only
if (ticksSinceStart >= 0 && ticksSinceStart <= START_DELAY) ticksSinceStart++;
if (startRequested && (ticksSinceStart < 0 || ticksSinceStart > START_DELAY)) {
startRequested = false;
if (!executor.isOn()) {
ticksSinceStart = 0;
executor.queueStart();
}
}
executor.tick();
// Update the environment's internal state.
internalEnvironment.tick();
// Propagate the environment's output to the world.
externalOutputChanges.accumulateAndGet(internalEnvironment.updateOutput(), (x, y) -> x | y);
}
/**
* Get a bitmask returning which sides on the computer have changed, resetting the internal state.
*
* @return What sides on the computer have changed.
*/
public int pollAndResetChanges() {
return externalOutputChanges.getAndSet(0);
}
public boolean isBlinking() {
if (!isOn() || !terminal.getCursorBlink()) return false;
var cursorX = terminal.getCursorX();
var cursorY = terminal.getCursorY();
return cursorX >= 0 && cursorX < terminal.getWidth() && cursorY >= 0 && cursorY < terminal.getHeight();
}
public void addApi(ILuaAPI api) {
executor.addApi(api);
}
long getUniqueTaskId() {
return lastTaskId.incrementAndGet();
}
}