started to work on moving architecture and machine interfaces to the public api

This commit is contained in:
Florian Nücke 2014-03-03 15:12:28 +01:00
parent d6b0eb9cae
commit bb9f073fc9
21 changed files with 619 additions and 247 deletions

View File

@ -0,0 +1,45 @@
package li.cil.oc.api;
import li.cil.oc.api.detail.MachineAPI;
import li.cil.oc.api.machine.Architecture;
import li.cil.oc.api.machine.Owner;
/**
* This API is intended for people who would like to implement custom computer
* blocks or anything else hosting a computer.
* <p/>
* It also allows registering new {@link li.cil.oc.api.machine.Architecture}s,
* which are implementations of specific languages (e.g. assembler). The built-
* in ones are available as static fields in this class.
* <p/>
* Note that these methods should <em>not</em> be called in the pre-init phase,
* since the {@link #instance} may not have been initialized at that time. Only
* start calling these methods in the init phase or later.
*/
public final class Machine {
/**
* The built-in architecture that uses the native Lua implementation.
*/
public static Class<? extends Architecture> NativeLuaArchitecture = null;
/**
* The built-in architecture that uses the LuaJ Lua implementation.
*/
public static Class<? extends Architecture> JavaLuaArchitecture = null;
void add(Class<? extends Architecture> architecture) {
if (instance != null) instance.add(architecture);
}
li.cil.oc.server.component.machine.Machine create(Owner owner, Class<? extends Architecture> architecture) {
if (instance != null) return instance.create(owner, architecture);
return null;
}
// ----------------------------------------------------------------------- //
private Machine() {
}
public static MachineAPI instance = null;
}

View File

@ -8,21 +8,118 @@ import li.cil.oc.api.fs.Label;
import li.cil.oc.api.network.ManagedEnvironment;
public interface FileSystemAPI {
/**
* Creates a new file system based on the location of a class.
* <p/>
* This can be used to wrap a folder in the assets folder of your mod's JAR.
* The actual path is built like this:
* <pre>"/assets/" + domain + "/" + root</pre>
* <p/>
* If the class is located in a JAR file, this will create a read-only file
* system based on that JAR file. If the class file is located in the native
* file system, this will create a read-only file system first trying from
* the actual location of the class file, and failing that by searching the
* class path (i.e. it'll look for a path constructed as described above).
* <p/>
* If the specified path cannot be located, the creation fails and this
* returns <tt>null</tt>.
*
* @param clazz the class whose containing JAR to wrap.
* @param domain the domain, usually your mod's ID.
* @param root an optional subdirectory.
* @return a file system wrapping the specified folder.
*/
FileSystem fromClass(Class<?> clazz, String domain, String root);
/**
* Creates a new <em>writable</em> file system in the save folder.
* <p/>
* This will create a folder, if necessary, and create a writable virtual
* file system based in that folder. The actual path is based in a sub-
* folder of the save folder. The actual path is built like this:
* <pre>"saves/" + WORLD_NAME + "/opencomputers/" + root</pre>
* The first part may differ, in particular for servers.
* <p/>
* Usually the name will be the address of the node used to represent the
* file system.
* <p/>
* Note that by default file systems are "buffered", meaning that any
* changes made to them are only saved to disk when the world is saved. This
* ensured that the file system contents do not go "out of sync" when the
* game crashes, but introduces additional memory overhead, since all files
* in the file system have to be kept in memory.
*
* @param root the name of the file system.
* @param capacity the amount of space in bytes to allow being used.
* @param buffered whether data should only be written to disk when saving.
* @return a file system wrapping the specified folder.
*/
FileSystem fromSaveDirectory(String root, long capacity, boolean buffered);
/**
* Creates a new <em>writable</em> file system that resides in memory.
* <p/>
* Any contents created and written on this file system will be lost when
* the node is removed from the network.
* <p/>
* This is used for computers' <tt>/tmp</tt> mount, for example.
*
* @param capacity the capacity of the file system.
* @return a file system residing in memory.
*/
FileSystem fromMemory(long capacity);
/**
* Creates a new file system based on a read-only ComputerCraft mount.
*
* @param mount the mount to wrap with a file system.
* @return a file system wrapping the specified mount.
*/
@Optional.Method(modid = "ComputerCraft")
FileSystem fromComputerCraft(IMount mount);
/**
* Creates a new file system based on a read-write ComputerCraft mount.
*
* @param mount the mount to wrap with a file system.
* @return a file system wrapping the specified mount.
*/
@Optional.Method(modid = "ComputerCraft")
FileSystem fromComputerCraft(IWritableMount mount);
ManagedEnvironment asManagedEnvironment(FileSystem fs, Label label);
/**
* Creates a network node that makes the specified file system available via
* the common file system driver.
* <p/>
* This can be useful for providing some data if you don't wish to implement
* your own driver. Which will probably be most of the time. If you need
* more control over the node, implement your own, and connect this one to
* it. In that case you will have to forward any disk driver messages to the
* node, though.
*
* @param fileSystem the file system to wrap.
* @param label the label of the file system.
* @return the network node wrapping the file system.
*/
ManagedEnvironment asManagedEnvironment(FileSystem fileSystem, Label label);
ManagedEnvironment asManagedEnvironment(FileSystem fs, String label);
/**
* Like {@link #asManagedEnvironment(li.cil.oc.api.fs.FileSystem, Label)},
* but creates a read-only label initialized to the specified value.
*
* @param fileSystem the file system to wrap.
* @param label the read-only label of the file system.
* @return the network node wrapping the file system.
*/
ManagedEnvironment asManagedEnvironment(FileSystem fileSystem, String label);
ManagedEnvironment asManagedEnvironment(FileSystem fs);
/**
* Like {@link #asManagedEnvironment(li.cil.oc.api.fs.FileSystem, Label)},
* but creates an unlabeled file system (i.e. the label can neither be read
* nor written).
*
* @param fileSystem the file system to wrap.
* @return the network node wrapping the file system.
*/
ManagedEnvironment asManagedEnvironment(FileSystem fileSystem);
}

View File

@ -0,0 +1,11 @@
package li.cil.oc.api.detail;
import li.cil.oc.api.machine.Architecture;
import li.cil.oc.api.machine.Owner;
import li.cil.oc.server.component.machine.Machine;
public interface MachineAPI {
void add(Class<? extends Architecture> architecture);
Machine create(Owner owner, Class<? extends Architecture> architecture);
}

View File

@ -6,9 +6,71 @@ import li.cil.oc.api.network.Visibility;
import net.minecraft.tileentity.TileEntity;
public interface NetworkAPI {
/**
* Tries to add a tile entity's network node(s) at the specified coordinates
* to adjacent networks.
* <p/>
* If the tile entity implements {@link Environment} its one node will be
* connected to any existing adjacent tile entity nodes. If none exist a
* new network with the specified tile entity's node as its sole entry.
* <p/>
* If the tile entity is a {@link li.cil.oc.api.network.SidedEnvironment}
* the same rules as for simple environments apply, except that the
* respective for each side is used when connecting, and each side's node
* is added to its own new network, if necessary.
*
* @param tileEntity the tile entity to initialize.
*/
void joinOrCreateNetwork(TileEntity tileEntity);
/**
* Creates a new network with the specified node as its initial node.
* <p/>
* This can be used to create networks that are not bound to any tile
* entity. For example, this is used to create the internal networks of
* robots.
*
* @param node the node to create the network for.
* @throws IllegalArgumentException if the node already is in a network.
*/
void joinNewNetwork(Node node);
/**
* Factory function for creating new nodes.
* <p/>
* Use this to create a node for your environment (e.g. tile entity). This
* will return a builder that can be used to further specialize the node,
* making it either a component node (for callbacks), a connector node
* (for power interaction) or both.
* <p/>
* Example use:
* <pre>
* class YourThing extends TileEntity implements Environment {
* private ComponentConnector node_ =
* api.Network.newNode(this, Visibility.Network).
* withComponent("your_thing").
* withConnector(32).
* create();
*
* public Node node() { return node_; }
*
* // ...
* }
* </pre>
* <p/>
* Note that the <em>reachability</em> specified here is the general
* availability of the created node to other nodes in the network. Special
* rules apply to components, which have a <em>visibility</em> that is used
* to control how they can be reached from computers. For example, network
* cards have a <em>reachability</em> of <tt>Visibility.Network</tt>, to
* allow them to communicate with each other, but a <em>visibility</em> of
* <tt>Visibility.Neighbors</tt> to avoid other computers in the network
* to see the card (i.e. only the user programs running on the computer the
* card installed in can see interact with it).
*
* @param host the environment the node is created for.
* @param reachability the reachability of the node.
* @return a new node builder.
*/
Builder.NodeBuilder newNode(Environment host, Visibility reachability);
}

View File

@ -15,7 +15,7 @@ public interface Processor extends Item {
* The additional number of components supported if this processor is
* installed in the server.
*
* @param stack
* @param stack the processor to get the number of supported components for.
* @return the number of additionally supported components.
*/
int supportedComponents(ItemStack stack);

View File

@ -0,0 +1,136 @@
package li.cil.oc.api.machine;
import net.minecraft.nbt.NBTTagCompound;
/**
* This interface abstracts away any language specific details for the Machine.
* <p/>
* This allows the introduction of other languages, e.g. computers that run
* assembly or some other language interpreter. The two architectures included
* in OpenComputers are the native Lua architecture (using native LuaC) and the
* Java Lua architecture (using LuaJ).
*/
public interface Architecture {
/**
* A display friendly name of the architecture,
* @return
*/
String name();
/**
* Used to check if the machine is fully initialized. If this is false no
* signals for detected components will be generated. Avoids duplicate
* signals if <tt>component_added</tt> signals are generated in the
* language's startup script, for already present components (see Lua's
* init.lua script).
* <p/>
* This is also used to check whether limits on direct calls should be
* enforced or not - this allows a quick boot phase in the language's
* kernel logic before switching to business-as-usual.
*
* @return whether the machine is fully initialized.
*/
boolean isInitialized();
/**
* This is called when the amount of memory in the machine may have changed.
* This is usually triggered by the owner when its composition changes. For
* example this is called from computer cases' onInventoryChanged method.
*/
void recomputeMemory();
/**
* Called when a machine starts up. Used to (re-)initialize the underlying
* architecture logic. For example, for Lua this creates a new Lua state.
* <p/>
* This also sets up any built-in APIs for the underlying language, such as
* querying available memory, listing and interacting with components and so
* on. If this returns <tt>false</tt> the machine fails to start.
* <p/>
* Note that the owning machine has not necessarily been connected to a
* network when this is called, in case this is called from the machine's
* load logic. Use {@link #onConnect()} for additional initialization that
* depends on a node network (such as connecting a ROM file system).
*
* @return whether the architecture was initialized successfully.
*/
boolean init();
/**
* Called when a machine stopped. Used to clean up any handles, memory and
* so on. For example, for Lua this destroys the Lua state.
*/
void close();
/**
* Performs a synchronized call initialized in a previous call to
* {@link #runThreaded(boolean)}.
* <p/>
* This method is invoked from the main server thread, meaning it is safe
* to interact with the world without having to perform manual
* synchronization.
* <p/>
* This method is expected to leave the architecture in a state so it is
* prepared to next be called with <tt>runThreaded(true)</tt>. For example,
* the Lua architecture will leave the results of the synchronized call on
* the stack so they can be further processed in the next call to
* <tt>runThreaded</tt>.
*/
void runSynchronized();
/**
* Continues execution of the machine. The first call may be used to
* initialize the machine (e.g. for Lua we load the libraries in the first
* call so that the computers boot faster). After that the architecture
* <em>should</em> return <tt>true</tt> from {@link #isInitialized()}.
* <p/>
* The resumed state is either a return from a synchronized call, when a
* synchronized call has been completed (via <tt>runSynchronized</tt>), or
* a normal yield in all other cases (sleep, interrupt, boot, ...).
* <p/>
* This is expected to return within a very short time, usually. For example,
* in Lua this returns as soon as the state yields, and returns at the latest
* when the Settings.timeout is reached (in which case it forces the state
* to crash).
* <p/>
* This is expected to consume a single signal if one is present and return.
* If returning from a synchronized call this should consume no signal.
*
* @param isSynchronizedReturn whether the architecture is resumed from an
* earlier synchronized call. In the case of
* Lua this means the results of the call are
* now on the stack, for example.
* @return the result of the execution. Used to determine the new state.
*/
ExecutionResult runThreaded(boolean isSynchronizedReturn);
/**
* Called when the owning machine was connected to the component network.
* <p/>
* This can be useful for connecting custom file systems (read only memory)
* in case {@link #init()} was called from the machine's load logic (where
* it was not yet connected to the network).
*/
void onConnect();
/**
* Restores the state of this architecture as previously saved in
* {@link #save(NBTTagCompound)}. The architecture should be in the same
* state it was when it was saved after this, so it can be resumed from
* whatever state the owning machine was in when it was saved.
*
* @param nbt the tag compound to save to.
*/
void load(NBTTagCompound nbt);
/**
* Saves the architecture for later restoration, e.g. across games or chunk
* unloads. Used to persist a machine's execution state. For native Lua this
* uses the Eris library to persist the main coroutine, for example.
* <p/>
* Note that the tag compound is shared with the Machine.
*
* @param nbt the tag compound to save to.
*/
void save(NBTTagCompound nbt);
}

View File

@ -0,0 +1,66 @@
package li.cil.oc.api.machine;
/**
* Used by the Machine to determine the result of a call to
* {@link Architecture#runThreaded()}.
* <p/>
* Do not implement this interface, only use the predefined internal classes.
*/
public abstract class ExecutionResult {
/**
* Indicates the machine may sleep for the specified number of ticks. This
* is merely considered a suggestion. If signals are in the queue or are
* pushed to the queue while sleeping, the sleep will be interrupted and
* {@link Architecture#runThreaded()} will be called so that the next signal
* is pushed.
*/
public static final class Sleep extends ExecutionResult {
/**
* The number of ticks to sleep.
*/
public final int ticks;
public Sleep(int ticks) {
this.ticks = ticks;
}
}
/**
* Indicates tha the computer should shutdown or reboot.
*/
public static final class Shutdown extends ExecutionResult {
/**
* Whether to reboot. If false the computer will stop.
*/
public final boolean reboot;
public Shutdown(boolean reboot) {
this.reboot = reboot;
}
}
/**
* Indicates that a synchronized call should be performed. The architecture
* is expected to be in a state that allows the next call to be to
* {@link Architecture#runSynchronized()} instead of
* {@link Architecture#runThreaded()}. This is used to perform calls from
* the server's main thread, to avoid threading issues when interacting with
* other objects in the world.
*/
public static final class SynchronizedCall extends ExecutionResult {
}
/**
* Indicates that an error occurred and the computer should crash.
*/
public static final class Error extends ExecutionResult {
/**
* The error message.
*/
public final String message;
public Error(String message) {
this.message = message;
}
}
}

View File

@ -0,0 +1,23 @@
package li.cil.oc.api.machine;
import li.cil.oc.api.network.Context;
import li.cil.oc.api.network.ManagedEnvironment;
public interface Machine extends ManagedEnvironment, Context {
/**
* The underlying architecture of the machine.
* <p/>
* This is what actually evaluates code running on the machine, where the
* machine class itself serves as a scheduler.
*
* @return the architecture of this machine.
*/
Architecture architecture();
/**
* The owner of the machine, usually a tile entity hosting the machine.
*
* @return the owner of the machine.
*/
Owner owner();
}

View File

@ -0,0 +1,73 @@
package li.cil.oc.api.machine;
import li.cil.oc.api.network.Context;
import li.cil.oc.api.network.Environment;
import li.cil.oc.api.network.Node;
import net.minecraft.world.World;
/**
* This interface has to be implemented by 'owners' of machine instances.
* <p/>
* It provides some context for the machine, in particular which world it is
* running in, to allow querying the time of day, for example.
*/
public interface Owner extends Context {
/**
* The world the machine is running in, e.g. if the owner is a tile entity
* this is the world the tile entity lives in.
*
* @return the world the machine runs in.
*/
World world();
/**
* The amount of memory (RAM) made available to the machine, in bytes.
* <p/>
* This is usually determined by the components installed in the owner, for
* example the memory sticks in a computer case.
*
* @return the amount of memory that can be used by the machine, in bytes.
*/
int installedMemory();
/**
* The number of components the machine can address without crashing.
* <p/>
* This is usually determined by the components installed in the owner, for
* example the CPUs in a server.
* <p/>
* Note that the component count does <em>not</em> include file systems.
*
* @return the number of supported components.
*/
int maxComponents();
/**
* This is called by the machine when its state changed (which can be
* multiple times per actual game tick), to notify the owner that it should
* save its state on the next world save.
* <p/>
* This method is called from executor threads, so it must be thread-safe.
*/
void markAsChanged();
/**
* This is called on the owner when the machine's {@link Environment#onConnect(Node)}
* method gets called. This can be useful for reacting to network events
* when the owner does not have its own node (for example, computer cases
* expose their machine's node as their own node). This callback allows it
* to connect its components (graphics cards and the like) when it is
* connected to a node network (when added to the world, for example).
*
* @param node the node that was connected to the network.
*/
void onMachineConnect(Node node);
/**
* Like {@link #onMachineConnect(Node)}, except that this is called whenever
* the machine's {@link Environment#onDisconnect(Node)} method is called.
*
* @param node the node that was disconnected from the network.
*/
void onMachineDisconnect(Node node);
}

View File

@ -79,7 +79,6 @@ public @interface Callback {
* more importantly you should document the expected parameters and return
* type here.
* <p/>
* TODO Actually implement for this to be available to computers...
*/
String doc() default "";
}

View File

@ -37,5 +37,5 @@
@cpw.mods.fml.common.API(
owner = "OpenComputers|Core",
provides = "OpenComputersAPI",
apiVersion = "1.4.0")
apiVersion = "1.4.1")
package li.cil.oc.api;

View File

@ -9,6 +9,7 @@ import li.cil.oc._
import li.cil.oc.common.asm.SimpleComponentTickHandler
import li.cil.oc.common.multipart.MultiPart
import li.cil.oc.server.component.Keyboard
import li.cil.oc.server.component.machine
import li.cil.oc.server.network.Network
import li.cil.oc.server.{TickHandler, driver, fs, network}
import li.cil.oc.util.WirelessNetwork
@ -26,6 +27,7 @@ class Proxy {
api.Driver.instance = driver.Registry
api.FileSystem.instance = fs.FileSystem
api.Machine.instance = machine.Machine
api.Network.instance = network.Network
}

View File

@ -3,6 +3,7 @@ package li.cil.oc.common.tileentity
import cpw.mods.fml.common.Optional
import cpw.mods.fml.relauncher.{Side, SideOnly}
import li.cil.oc.Settings
import li.cil.oc.api.machine.Owner
import li.cil.oc.api.network._
import li.cil.oc.server.component.machine.Machine
import li.cil.oc.server.{PacketSender => ServerPacketSender, driver}
@ -17,7 +18,7 @@ import stargatetech2.api.bus.IBusDevice
// See AbstractBusAware as to why we have to define the IBusDevice here.
@Optional.Interface(iface = "stargatetech2.api.bus.IBusDevice", modid = "StargateTech2")
abstract class Computer(isRemote: Boolean) extends Environment with ComponentInventory with Rotatable with BundledRedstoneAware with AbstractBusAware with IBusDevice with Analyzable with Machine.Owner {
abstract class Computer(isRemote: Boolean) extends Environment with ComponentInventory with Rotatable with BundledRedstoneAware with AbstractBusAware with IBusDevice with Analyzable with Owner {
protected val _computer = if (isRemote) null else new Machine(this)
def computer = _computer
@ -71,6 +72,10 @@ abstract class Computer(isRemote: Boolean) extends Environment with ComponentInv
case Some(component) => component
}
override def onMachineConnect(node: Node) = this.onConnect(node)
override def onMachineDisconnect(node: Node) = this.onDisconnect(node)
def hasAbstractBusCard = items.exists {
case Some(item) => computer.isRunning && driver.item.AbstractBusCard.worksWith(item)
case _ => false

View File

@ -415,7 +415,7 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w
// ----------------------------------------------------------------------- //
override def onConnect(node: Node) {
override def onMachineConnect(node: Node) {
super.onConnect(node)
if (node == this.node) {
computer.node.connect(buffer.node)
@ -434,7 +434,7 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w
}
}
override def onDisconnect(node: Node) {
override def onMachineDisconnect(node: Node) {
super.onDisconnect(node)
if (node == this.node) {
buffer.node.remove()

View File

@ -3,6 +3,7 @@ package li.cil.oc.server.component
import li.cil.oc.Items
import li.cil.oc.api.driver
import li.cil.oc.api.driver.Slot
import li.cil.oc.api.machine.Owner
import li.cil.oc.api.network.{Message, Node}
import li.cil.oc.common.inventory.ComponentInventory
import li.cil.oc.common.inventory.ServerInventory
@ -14,7 +15,7 @@ import li.cil.oc.util.ExtendedNBT._
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
class Server(val rack: tileentity.Rack, val number: Int) extends Machine.Owner {
class Server(val rack: tileentity.Rack, val number: Int) extends Owner {
val machine = new Machine(this)
val inventory = new NetworkedInventory()
@ -76,9 +77,9 @@ class Server(val rack: tileentity.Rack, val number: Int) extends Machine.Owner {
// ----------------------------------------------------------------------- //
override def onConnect(node: Node) = inventory.onConnect(node)
override def onMachineConnect(node: Node) = inventory.onConnect(node)
override def onDisconnect(node: Node) = inventory.onDisconnect(node)
override def onMachineDisconnect(node: Node) = inventory.onDisconnect(node)
def load(nbt: NBTTagCompound) {
machine.load(nbt.getCompoundTag("machine"))

View File

@ -1,101 +0,0 @@
package li.cil.oc.server.component.machine
import net.minecraft.nbt.NBTTagCompound
/**
* This trait abstracts away any language specific details for the Machine.
*
* At some point in the future this may allow us to introduce other languages,
* e.g. computers that run assembly or non-persistent computers that use a pure
* Java implementation of Lua.
*/
trait Architecture {
/**
* Used to check if the machine is fully initialized. If this is false no
* signals for detected components will be generated. Avoids duplicate signals
* if component_added signals are generated in the language's startup script,
* for already present components (see Lua's init.lua script).
*
* @return whether the machine is fully initialized.
*/
def isInitialized: Boolean
/**
* This is called when the amount of memory in the computer may have changed.
* It is triggered by the tile entity's onInventoryChanged.
*/
def recomputeMemory()
/**
* Performs a synchronized call initialized in a previous call to runThreaded.
*/
def runSynchronized()
/**
* Continues execution of the machine. The first call may be used to
* initialize the machine (e.g. for Lua we load the libraries in the first
* call so that the computers boot faster).
*
* The resumed state is either Machine.State.SynchronizedReturn, when a
* synchronized call has been completed (via runSynchronized), or
* Machine.State.Yielded in all other cases (sleep, interrupt, boot, ...).
*
* This is expected to return within a very short time, usually. For example,
* in Lua this returns as soon as the state yields, and returns at the latest
* when the Settings.timeout is reached (in which case it forces the state
* to crash).
*
* This is expected to consume a single signal if one is present and return.
* If returning from a synchronized call this should consume no signal.
*
* @param enterState the state that is being resumed.
* @return the result of the execution. Used to determine the new state.
*/
def runThreaded(enterState: Machine.State.Value): ExecutionResult
/**
* Called when a computer starts up. Used to (re-)initialize the underlying
* architecture logic. For example, for Lua the creates a new Lua state.
*
* This also sets up any built-in APIs for the underlying language, such as
* querying available memory, listing and interacting with components and so
* on. If this returns false the computer fails to start.
*
* @return whether the architecture was initialized successfully.
*/
def init(): Boolean
/**
* Called when the owning machine was connected to the component network.
*
* This can be useful for connecting custom file systems (read only memory)
* in case init() was called from the machine's load() method (where it was
* not yet connected to the network).
*/
def onConnect()
/**
* Called when a computer stopped. Used to clean up any handles, memory and
* so on. For example, for Lua this destroys the Lua state.
*/
def close()
/**
* Restores the state of this architecture as previously saved in save().
*
* @param nbt the tag compound to save to.
*/
def load(nbt: NBTTagCompound)
/**
* Saves the architecture for later restoration, e.g. across games or chunk
* unloads. Used to persist a computer's executions state. For native Lua this
* uses the Eris library to persist the main coroutine.
*
* Note that the tag compound is shared with the Machine, so collisions have
* to be avoided (see Machine.save for used keys).
*
* @param nbt the tag compound to save to.
*/
def save(nbt: NBTTagCompound)
}

View File

@ -1,45 +0,0 @@
package li.cil.oc.server.component.machine
/**
* Used by the Machine to determine the result of a call to runThreaded.
*
* Do not implement this interface, only use the predefined classes below.
*/
trait ExecutionResult
object ExecutionResult {
/**
* Indicates the machine may sleep for the specified number of ticks. This is
* merely considered a suggestion. If signals are in the queue or are pushed
* to the queue while sleeping, the sleep will be interrupted and runThreaded
* will be called so that the next signal is pushed.
*
* @param ticks the number of ticks to sleep.
*/
class Sleep(val ticks: Int) extends ExecutionResult
/**
* Indicates tha the computer should shutdown or reboot.
*
* @param reboot whether to reboot. If false the computer will stop.
*/
class Shutdown(val reboot: Boolean) extends ExecutionResult
/**
* Indicates that a synchronized call should be performed. The architecture
* is expected to be in a state that allows the next call to be to
* runSynchronized instead of runThreaded. This is used to perform calls from
* the server's main thread, to avoid threading issues when interacting with
* other objects in the world.
*/
class SynchronizedCall extends ExecutionResult
/**
* Indicates that an error occurred and the computer should crash.
*
* @param message the error message.
*/
class Error(val message: String) extends ExecutionResult
}

View File

@ -1,6 +1,7 @@
package li.cil.oc.server.component.machine
import li.cil.oc.api.FileSystem
import li.cil.oc.api.machine.Architecture
import li.cil.oc.util.ExtendedNBT._
import li.cil.oc.{Settings, OpenComputers}
import net.minecraft.nbt.NBTTagCompound

View File

@ -1,8 +1,8 @@
package li.cil.oc.server.component.machine
import Machine.State
import java.io.{IOException, FileNotFoundException}
import java.util.logging.Level
import li.cil.oc.api.machine.ExecutionResult
import li.cil.oc.util.ScalaClosure._
import li.cil.oc.util.{ScalaClosure, GameTimeFormatter}
import li.cil.oc.{OpenComputers, server, Settings}
@ -33,6 +33,8 @@ class LuaJLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) {
// ----------------------------------------------------------------------- //
override def name() = "LuaJ"
override def isInitialized = doneWithInitRun
override def recomputeMemory() = memory = machine.owner.installedMemory
@ -44,38 +46,37 @@ class LuaJLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) {
synchronizedCall = null
}
override def runThreaded(enterState: State.Value) = {
override def runThreaded(isSynchronizedReturn: Boolean) = {
try {
// Resume the Lua state and remember the number of results we get.
val results = enterState match {
case Machine.State.SynchronizedReturn =>
// If we were doing a synchronized call, continue where we left off.
val result = thread.resume(synchronizedResult)
synchronizedResult = null
result
case Machine.State.Yielded =>
if (!doneWithInitRun) {
// We're doing the initialization run.
val result = thread.resume(LuaValue.NONE)
// Mark as done *after* we ran, to avoid switching to synchronized
// calls when we actually need direct ones in the init phase.
doneWithInitRun = true
// We expect to get nothing here, if we do we had an error.
if (result.narg == 1) {
// Fake zero sleep to avoid stopping if there are no signals.
LuaValue.varargsOf(LuaValue.TRUE, LuaValue.valueOf(0))
}
else {
LuaValue.NONE
}
val results = if (isSynchronizedReturn) {
// If we were doing a synchronized call, continue where we left off.
val result = thread.resume(synchronizedResult)
synchronizedResult = null
result
}
else {
if (!doneWithInitRun) {
// We're doing the initialization run.
val result = thread.resume(LuaValue.NONE)
// Mark as done *after* we ran, to avoid switching to synchronized
// calls when we actually need direct ones in the init phase.
doneWithInitRun = true
// We expect to get nothing here, if we do we had an error.
if (result.narg == 1) {
// Fake zero sleep to avoid stopping if there are no signals.
LuaValue.varargsOf(LuaValue.TRUE, LuaValue.valueOf(0))
}
else machine.popSignal() match {
case Some(signal) =>
thread.resume(LuaValue.varargsOf(Array(LuaValue.valueOf(signal.name)) ++ signal.args.map(ScalaClosure.toLuaValue)))
case _ =>
thread.resume(LuaValue.NONE)
else {
LuaValue.NONE
}
case s => throw new AssertionError("Running computer from invalid state " + s.toString)
}
else machine.popSignal() match {
case Some(signal) =>
thread.resume(LuaValue.varargsOf(Array(LuaValue.valueOf(signal.name)) ++ signal.args.map(ScalaClosure.toLuaValue)))
case _ =>
thread.resume(LuaValue.NONE)
}
}
// Check if the kernel is still alive.

View File

@ -1,6 +1,7 @@
package li.cil.oc.server.component.machine
import java.util.logging.Level
import li.cil.oc.api.machine.{Architecture, Owner, ExecutionResult}
import li.cil.oc.api.network._
import li.cil.oc.api.{FileSystem, Network}
import li.cil.oc.common.tileentity
@ -14,11 +15,11 @@ import net.minecraft.entity.player.EntityPlayer
import net.minecraft.nbt._
import net.minecraft.server.MinecraftServer
import net.minecraft.server.integrated.IntegratedServer
import net.minecraft.world.World
import scala.Array.canBuildFrom
import scala.collection.mutable
import li.cil.oc.api.detail.MachineAPI
class Machine(val owner: Machine.Owner) extends ManagedComponent with Context with Runnable {
class Machine(val owner: Owner) extends ManagedComponent with Context with Runnable {
val node = Network.newNode(this, Visibility.Network).
withComponent("computer", Visibility.Neighbors).
withConnector(if (isRobot) Settings.get.bufferRobot + 30 * Settings.get.bufferPerLevel else Settings.get.bufferComputer).
@ -432,7 +433,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
}
}
// For computers, to generate the components in their inventory.
owner.onConnect(node)
owner.onMachineConnect(node)
}
override def onDisconnect(node: Node) {
@ -447,7 +448,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
}
}
// For computers, to save the components in their inventory.
owner.onDisconnect(node)
owner.onMachineDisconnect(node)
}
// ----------------------------------------------------------------------- //
@ -684,7 +685,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
// This is a really high level lock that we only use for saving and loading.
override def run(): Unit = Machine.this.synchronized {
val enterState = state.synchronized {
val isSynchronizedReturn = state.synchronized {
if (state.top != Machine.State.Yielded &&
state.top != Machine.State.SynchronizedReturn) {
return
@ -694,13 +695,13 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
state.push(Machine.State.Paused)
return
}
switchTo(Machine.State.Running)
switchTo(Machine.State.Running) == Machine.State.SynchronizedReturn
}
cpuStart = System.nanoTime()
try {
val result = architecture.runThreaded(enterState)
val result = architecture.runThreaded(isSynchronizedReturn)
// Check if someone called pause() or stop() in the meantime.
state.synchronized {
@ -752,7 +753,15 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
}
}
object Machine {
object Machine extends MachineAPI {
val architectures = mutable.Set.empty[Class[_ <: Architecture]]
override def add(architecture: Class[_ <: Architecture]) {
}
override def create(owner: Owner, architecture: Class[_ <: Architecture]) = ???
private[component] class LimitReachedException extends Exception
@ -793,19 +802,4 @@ object Machine {
private[component] class Signal(val name: String, val args: Array[Any])
private val threadPool = ThreadPoolFactory.create("Computer", Settings.get.threads)
trait Owner extends Context {
def installedMemory: Int
def maxComponents: Int
def world: World
def markAsChanged()
def onConnect(node: Node)
def onDisconnect(node: Node)
}
}

View File

@ -4,6 +4,7 @@ import com.google.common.base.Strings
import com.naef.jnlua._
import java.io.{IOException, FileNotFoundException}
import java.util.logging.Level
import li.cil.oc.api.machine.ExecutionResult
import li.cil.oc.util.ExtendedLuaState.extendLuaState
import li.cil.oc.util.{GameTimeFormatter, LuaStateFactory}
import li.cil.oc.{OpenComputers, server, Settings}
@ -29,6 +30,8 @@ class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) {
// ----------------------------------------------------------------------- //
override def name() = "Lua"
override def isInitialized = kernelMemory > 0
override def recomputeMemory() = Option(lua) match {
@ -65,7 +68,7 @@ class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) {
}
}
override def runThreaded(enterState: Machine.State.Value): ExecutionResult = {
override def runThreaded(isSynchronizedReturn: Boolean): ExecutionResult = {
try {
// The kernel thread will always be at stack index one.
assert(lua.isThread(1))
@ -77,44 +80,43 @@ class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) {
}
// Resume the Lua state and remember the number of results we get.
val results = enterState match {
case Machine.State.SynchronizedReturn =>
// If we were doing a synchronized call, continue where we left off.
assert(lua.getTop == 2)
assert(lua.isTable(2))
lua.resume(1, 1)
case Machine.State.Yielded =>
if (kernelMemory == 0) {
// We're doing the initialization run.
if (lua.resume(1, 0) > 0) {
// We expect to get nothing here, if we do we had an error.
0
}
else {
// Run the garbage collector to get rid of stuff left behind after
// the initialization phase to get a good estimate of the base
// memory usage the kernel has (including libraries). We remember
// that size to grant user-space programs a fixed base amount of
// memory, regardless of the memory need of the underlying system
// (which may change across releases).
lua.gc(LuaState.GcAction.COLLECT, 0)
kernelMemory = math.max(lua.getTotalMemory - lua.getFreeMemory, 1)
recomputeMemory()
val results = if (isSynchronizedReturn) {
// If we were doing a synchronized call, continue where we left off.
assert(lua.getTop == 2)
assert(lua.isTable(2))
lua.resume(1, 1)
}
else {
if (kernelMemory == 0) {
// We're doing the initialization run.
if (lua.resume(1, 0) > 0) {
// We expect to get nothing here, if we do we had an error.
0
}
else {
// Run the garbage collector to get rid of stuff left behind after
// the initialization phase to get a good estimate of the base
// memory usage the kernel has (including libraries). We remember
// that size to grant user-space programs a fixed base amount of
// memory, regardless of the memory need of the underlying system
// (which may change across releases).
lua.gc(LuaState.GcAction.COLLECT, 0)
kernelMemory = math.max(lua.getTotalMemory - lua.getFreeMemory, 1)
recomputeMemory()
// Fake zero sleep to avoid stopping if there are no signals.
lua.pushInteger(0)
1
}
// Fake zero sleep to avoid stopping if there are no signals.
lua.pushInteger(0)
1
}
else machine.popSignal() match {
case Some(signal) =>
lua.pushString(signal.name)
signal.args.foreach(arg => lua.pushValue(arg))
lua.resume(1, 1 + signal.args.length)
case _ =>
lua.resume(1, 0)
}
case s => throw new AssertionError("Running computer from invalid state " + s.toString)
}
else machine.popSignal() match {
case Some(signal) =>
lua.pushString(signal.name)
signal.args.foreach(arg => lua.pushValue(arg))
lua.resume(1, 1 + signal.args.length)
case _ =>
lua.resume(1, 0)
}
}
// Check if the kernel is still alive.