blob: 0f82b234eac735cfe1c31dd5d9a608c6dea7e773 [file] [log] [blame] [raw]
package gamax92.thistle;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.io.IOUtils;
import com.loomcom.symon.Cpu;
import cpw.mods.fml.common.FMLCommonHandler;
import gamax92.thistle.devices.BankSelector;
import gamax92.thistle.exceptions.CallSynchronizedException;
import gamax92.thistle.exceptions.CallSynchronizedException.Cleanup;
import gamax92.thistle.util.ValueManager;
import li.cil.oc.Settings;
import li.cil.oc.api.Driver;
import li.cil.oc.api.driver.item.Processor;
import li.cil.oc.api.driver.item.Memory;
import li.cil.oc.api.machine.Architecture;
import li.cil.oc.api.machine.Callback;
import li.cil.oc.api.machine.ExecutionResult;
import li.cil.oc.api.machine.LimitReachedException;
import li.cil.oc.api.machine.Machine;
import li.cil.oc.api.machine.Signal;
import li.cil.oc.api.machine.Value;
import li.cil.oc.api.network.Component;
import li.cil.oc.api.network.Node;
import li.cil.oc.common.SaveHandler;
import li.cil.oc.server.PacketSender;
import li.cil.oc.server.machine.Callbacks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import scala.Option;
@Architecture.Name("6502 Thistle")
public class ThistleArchitecture implements Architecture {
private final Machine machine;
private ThistleVM vm;
private boolean initialized = false;
private boolean inSynchronizedCall = false;
private CallSynchronizedException syncCall;
/** The constructor must have exactly this signature. */
public ThistleArchitecture(Machine machine) {
this.machine = machine;
}
@Override
public boolean isInitialized() {
return initialized;
}
private static int calculateMemory(Iterable<ItemStack> components) {
int memory = 0;
for (ItemStack component : components) {
if (Driver.driverFor(component) instanceof Memory) {
Memory memdrv = (Memory) Driver.driverFor(component);
memory += memdrv.amount(component) * 1024;
}
}
return Math.min(Math.max(memory, 0), Settings.get().maxTotalRam());
}
@Override
public boolean recomputeMemory(Iterable<ItemStack> components) {
if (vm != null) {
int memory = calculateMemory(components);
vm.machine.resize(memory);
}
return true;
}
@Override
public boolean initialize() {
// Set up new VM here
vm = new ThistleVM(machine);
BankSelector banksel = this.vm.machine.getBankSelector();
int memory = calculateMemory(this.machine.host().internalComponents());
vm.machine.resize(memory);
vm.machine.reset();
for (ItemStack component : machine.host().internalComponents()) {
if (Driver.driverFor(component) instanceof Processor) {
vm.cyclesPerTick = ThistleConfig.debugCpuSlowDown ? 10 : (Driver.driverFor(component).tier(component) + 1) * ThistleConfig.clocksPerTick;
break;
}
}
try {
PacketSender.sendSound(machine.host().world(), machine.host().xPosition(), machine.host().yPosition(), machine.host().zPosition(), ".");
} catch (Throwable e) {
}
initialized = true;
return true;
}
@Override
public void close() {
ValueManager.removeAll(this.machine);
if (vm != null) {
FMLCommonHandler.instance().bus().unregister(vm.machine);
vm = null;
}
}
@Override
public ExecutionResult runThreaded(boolean isSynchronizedReturn) {
try {
if (!isSynchronizedReturn) {
// Since our machine is a memory mapped one, parse signals here
Signal signal = null;
while (true) {
signal = machine.popSignal();
if (signal != null) {
vm.machine.getBus().onSignal(signal);
} else
break;
}
}
vm.run();
return new ExecutionResult.Sleep(0);
} catch (CallSynchronizedException e) {
if (e.getCleanup() != null) {
if (ThistleConfig.debugCpuTraceLog) // Exceptions thrown cause ThistleVM to skip trace logging.
Thistle.log.info("[Cpu] " + vm.machine.getCpu().getCpuState().toTraceEvent());
syncCall = e;
}
return new ExecutionResult.SynchronizedCall();
} catch (LimitReachedException e) {
return new ExecutionResult.SynchronizedCall();
} catch (Throwable t) {
t.printStackTrace();
return new ExecutionResult.Error(t.toString());
}
}
@Override
public void runSynchronized() {
if (syncCall != null) {
// Nice clean method for us to avoid multiple bus writes
Object thing = syncCall.getThing();
Cleanup cleanup = syncCall.getCleanup();
try {
Object[] results = null;
if (thing instanceof String)
results = machine.invoke((String) thing, syncCall.getMethod(), syncCall.getArgs());
else if (thing instanceof Value)
results = machine.invoke((Value) thing, syncCall.getMethod(), syncCall.getArgs());
cleanup.run(results, machine);
} catch (Exception e) {
cleanup.error(e);
}
cleanup.finish();
syncCall = null;
} else {
// Attempt to invoke again by re-executing the last instruction
inSynchronizedCall = true;
Cpu cpu = vm.machine.getCpu();
cpu.getCpuState().pc = cpu.getCpuState().lastPc;
cpu.step();
inSynchronizedCall = false;
}
}
@Override
public void onSignal() {
}
@Override
public void onConnect() {
}
public Object[] invoke(String address, String method, Object[] args) throws Exception {
if (!inSynchronizedCall) {
Node node = machine.node().network().node(address);
if (node instanceof Component) {
Callback callback = ((Component) node).annotation(method);
if (callback != null && !callback.direct())
throw new CallSynchronizedException(address, method, args);
}
}
try {
return machine.invoke(address, method, args);
} catch (LimitReachedException e) {
throw new CallSynchronizedException(address, method, args);
}
}
public Object[] invoke(Value value, String method, Object[] args) throws Exception {
if (!inSynchronizedCall) {
Option<Callbacks.Callback> option = Callbacks.apply(value).get(method);
if (option != null) {
Callbacks.Callback callback = option.get();
if (callback != null && !callback.annotation().direct())
throw new CallSynchronizedException(value, method, args);
}
}
try {
return machine.invoke(value, method, args);
} catch (LimitReachedException e) {
throw new CallSynchronizedException(value, method, args);
}
}
@Override
public void load(NBTTagCompound nbt) {
// Restore Machine
// Restore Memory
byte[] gzmem = SaveHandler.load(nbt, this.machine.node().address() + "_memory");
if (gzmem.length > 0) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(gzmem);
GZIPInputStream gzis = new GZIPInputStream(bais);
byte[] mem = IOUtils.toByteArray(gzis);
IOUtils.closeQuietly(gzis);
vm.machine.resize(mem.length);
for (int i = 0; i < mem.length; i++)
vm.machine.writeMem(i, mem[i]);
} catch (IOException e) {
Thistle.log.error("Failed to decompress memory from disk.");
e.printStackTrace();
}
}
// Restore CPU
if (nbt.hasKey("cpu")) {
Cpu mCPU = vm.machine.getCpu();
NBTTagCompound cpuTag = nbt.getCompoundTag("cpu");
mCPU.setAccumulator(cpuTag.getInteger("rA"));
mCPU.setProcessorStatus(cpuTag.getInteger("rP"));
mCPU.setProgramCounter(cpuTag.getInteger("rPC"));
mCPU.setStackPointer(cpuTag.getInteger("rSP"));
mCPU.setXRegister(cpuTag.getInteger("rX"));
mCPU.setYRegister(cpuTag.getInteger("rY"));
}
// Restore Values
if (nbt.hasKey("values"))
ValueManager.load(nbt.getCompoundTag("values"), this.machine);
vm.machine.getBus().load(nbt);
}
@Override
public void save(NBTTagCompound nbt) {
// Persist Machine
// Persist Memory
byte mem[] = new byte[vm.machine.getMemsize()];
for (int i = 0; i < mem.length; i++)
mem[i] = vm.machine.readMem(i);
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = new GZIPOutputStream(baos);
gzos.write(mem);
gzos.close();
SaveHandler.scheduleSave(machine.host(), nbt, machine.node().address() + "_memory", baos.toByteArray());
} catch (IOException e) {
Thistle.log.error("Failed to compress memory to disk");
e.printStackTrace();
}
// Persist CPU
Cpu mCPU = vm.machine.getCpu();
if (mCPU != null) {
NBTTagCompound cpuTag = new NBTTagCompound();
cpuTag.setInteger("rA", mCPU.getAccumulator());
cpuTag.setInteger("rP", mCPU.getProcessorStatus());
cpuTag.setInteger("rPC", mCPU.getProgramCounter());
cpuTag.setInteger("rSP", mCPU.getStackPointer());
cpuTag.setInteger("rX", mCPU.getXRegister());
cpuTag.setInteger("rY", mCPU.getYRegister());
nbt.setTag("cpu", cpuTag);
}
// Persist Values
NBTTagCompound valueTag = new NBTTagCompound();
ValueManager.save(valueTag, this.machine);
nbt.setTag("values", valueTag);
vm.machine.getBus().save(nbt);
}
}