diff --git a/assets/opencomputers/lua/boot.lua b/assets/opencomputers/lua/boot.lua index fbab782d2..daa5236a3 100644 --- a/assets/opencomputers/lua/boot.lua +++ b/assets/opencomputers/lua/boot.lua @@ -58,7 +58,7 @@ do end end end - wrapRecursive(drivers) + wrapRecursive(driver) end --[[ Permanent value tables. diff --git a/assets/opencomputers/lua/kernel.lua b/assets/opencomputers/lua/kernel.lua index 2433400d1..e5881312a 100644 --- a/assets/opencomputers/lua/kernel.lua +++ b/assets/opencomputers/lua/kernel.lua @@ -72,6 +72,8 @@ local function buildSandbox() yield = coroutine.yield }, + driver = driver, + math = { abs = math.abs, acos = math.acos, @@ -139,6 +141,8 @@ local function buildSandbox() unpack = table.unpack } } + + -- Make the sandbox its own globals table. sandbox._G = sandbox -- Allow sandboxes to load code, but only in text form, and in the sandbox. diff --git a/com/naef/jnlua/LuaState.java b/com/naef/jnlua/LuaState.java index e4d1f3592..0207b0bf5 100644 --- a/com/naef/jnlua/LuaState.java +++ b/com/naef/jnlua/LuaState.java @@ -750,7 +750,7 @@ public class LuaState { */ public synchronized void pushJavaObject(Object object) { check(); - getConverter().convertJavaObject(this, object); + converter.convertJavaObject(this, object); } /** @@ -1961,6 +1961,23 @@ public class LuaState { throw getArgException(index, String.format("invalid option '%s'", s)); } + /** + * Checks if the value of the specified function argument is a boolean. If + * so, the argument value is returned as a boolean. Otherwise, the method + * throws a Lua runtime exception with a descriptive error message. + * + * @param index + * the argument index + * @return the integer value + */ + public synchronized boolean checkBoolean(int index) { + check(); + if (!isBoolean(index)) { + throw getArgTypeException(index, LuaType.BOOLEAN); + } + return toBoolean(index); + } + /** * Checks if the value of the specified function argument is a number or a * string convertible to a number. If so, the argument value is returned as diff --git a/li/cil/oc/Config.scala b/li/cil/oc/Config.scala index 13fe0da18..b5fdc2dcc 100644 --- a/li/cil/oc/Config.scala +++ b/li/cil/oc/Config.scala @@ -5,6 +5,7 @@ object Config { var blockMonitorId = 3651 var itemHDDId = 4600 + var itemGPUId = 4601 var threads = 4 } \ No newline at end of file diff --git a/li/cil/oc/OpenComputers.scala b/li/cil/oc/OpenComputers.scala index b86c7f16c..bc3a0083e 100644 --- a/li/cil/oc/OpenComputers.scala +++ b/li/cil/oc/OpenComputers.scala @@ -1,5 +1,7 @@ package li.cil.oc +import java.util.logging.Logger + import cpw.mods.fml.common.Mod import cpw.mods.fml.common.Mod.EventHandler import cpw.mods.fml.common.SidedProxy @@ -12,6 +14,9 @@ import li.cil.oc.common.CommonProxy @Mod(modid = "OpenComputers", name = "OpenComputers", version = "0.0.0", dependencies = "required-after:Forge@[9.10.0.804,)", modLanguage = "scala") @NetworkMod(clientSideRequired = true, serverSideRequired = false) object OpenComputers { + /** Logger used all throughout this mod. */ + val log = Logger.getLogger("OpenComputers") + @SidedProxy( clientSide = "li.cil.oc.client.ClientProxy", serverSide = "li.cil.oc.common.CommonProxy") diff --git a/li/cil/oc/api/Callback.java b/li/cil/oc/api/Callback.java index e7d537e93..3d2224d20 100644 --- a/li/cil/oc/api/Callback.java +++ b/li/cil/oc/api/Callback.java @@ -6,29 +6,39 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * This annotation is used by components to mark methods that are part of the - * API they expose to computers. + * This annotation is used by {@see IDriver}s to mark methods that are part of + * the API they expose to computers. * - * If the component provides an API, each method annotated like this an entry in - * the API table will be generated. The actual entry will be a wrapper that + * If the driver provides an API, for each method annotated like this an entry + * in the API table will be generated. The actual entry will be a wrapper that * takes care of parameter transformation and all the Lua protocol stuff. This - * does limit the types you can use for parameters and return types, however. + * requires fixed parameters and return types, however, i.e. one parameter will + * always have to be one specific type (which is not necessary in Lua since it + * is a dynamic language). * - * Supported parameter types are: Boolean, Integer, Double and String. These map - * to boolean, number, number, and string respectively. If a function parameter - * is of an unsupported type its value will always be the type's default value. + * Any type is supported as long as it can be converted by JNLua's {@see + * com.naef.jnlua.DefaultConverter}. However, so as not to break persistence + * (i.e. saving a computer's state and seamlessly resume after reloading) only + * simple types such as booleans, numbers and strings and table types such as + * Map,List and arrays should be passed along. * - * Supported return types are: Boolean, Integer, Double, String and Array. These - * map to the same types as parameter types, with the exception of arrays which - * are interpreted as tuples, meaning they will be dissolved into multiple - * return values. If a return value type is of an unsupported type it will be - * ignored. + * If you wish more flexibility in how you handle parameters, define the + * callback with the following signature: + * + *
+ * Object[] callback(IComputerContext, Object[]) + *+ * + * In this case the first argument is the computer from which the function was + * called, the second is the array of objects passed along from Lua, which may + * be an arbitrary number of arbitrarily typed object (although still only basic + * types as described above are supported). The returned array must also contain + * only basic types as described above and represents the values returned as + * results to Lua as with automatically wrapped functions. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Callback { - /** - * The name under which the method will be available in Lua. - */ + /** The name under which the method will be available in Lua. */ String name(); } \ No newline at end of file diff --git a/li/cil/oc/api/ComponentType.scala b/li/cil/oc/api/ComponentType.scala index fdcdb5211..4b8a38163 100644 --- a/li/cil/oc/api/ComponentType.scala +++ b/li/cil/oc/api/ComponentType.scala @@ -12,5 +12,4 @@ object ComponentType extends Enumeration { val RAM = Value("RAM") val HDD = Value("HDD") val PCI = Value("PCI") - val GPU = Value("GPU") } \ No newline at end of file diff --git a/li/cil/oc/api/IBlockDriver.scala b/li/cil/oc/api/IBlockDriver.scala index e2efa0b60..6a4659fb2 100644 --- a/li/cil/oc/api/IBlockDriver.scala +++ b/li/cil/oc/api/IBlockDriver.scala @@ -9,22 +9,34 @@ import net.minecraft.block.Block * placed in the world, and particularly: next to computers. An example for * this are external drives, monitors and modems. * - * When a block component is added next to a computer, the computer's OS will - * be notified via a signal so that it may install the component's driver, for - * example. After that the OS may start to interact with the component via the - * API functions it provides. + * When a block component is placed next to a computer, the list of registered + * drivers is queried using the drivers' {@see #worksWith} functions. The first + * driver that replies positively will be used as the component's driver and + * the component will be installed. If no driver is found the item will be + * ignored. + * + * The computer will store a list of installed components, the values of which + * are based on what the driver returns from its {@see #component} function + * at the point of time the component is installed. + * If a driver's API function queries a component via the context using + * {@see IComputerContext#component()} the returned value will be exactly that. + * + * Note that it is possible to write one driver that supports as many different + * blocks as you wish. I'd recommend writing one per device (type), though, to + * keep things modular and the {@see IDriver#componentName} more meaningful. */ trait IBlockDriver extends IDriver { /** - * The type of block this driver handles. + * Used to determine the block types this driver handles. * - * When a block is added next to a computer and has this type, this driver - * will be used for the block. The return value must not change over the - * lifetime of this driver. + * This is used to determine which driver to use for a block placed next to a + * computer. Note that the return value should not change over time; if it + * does, though, an already installed component will not be ejected, since + * this value is only checked when adding components. * - * @return the block type this driver is used for. + * param block the block type to check for. */ - def blockType: Block + def worksWith(block: Block): Boolean /** * Get a reference to the actual component. @@ -38,5 +50,5 @@ trait IBlockDriver extends IDriver { * @param z the Z coordinate of the block to get the component for. * @return the block component at that location, controlled by this driver. */ - def getComponent(x: Int, y: Int, z: Int): Object + def component(x: Int, y: Int, z: Int): Object } \ No newline at end of file diff --git a/li/cil/oc/api/IDriver.scala b/li/cil/oc/api/IDriver.scala index 7d4104112..885bb6941 100644 --- a/li/cil/oc/api/IDriver.scala +++ b/li/cil/oc/api/IDriver.scala @@ -1,5 +1,7 @@ package li.cil.oc.api +import java.io.InputStream + /** * This interface specifies the structure of a driver for a component. * @@ -7,6 +9,16 @@ package li.cil.oc.api * used as computer components. They specify an API that is injected into the * Lua state when the driver is installed, and provide general information used * by the computer. + * + * Note that drivers themselves are singletons. They can define a parameter of + * type {@see IComputerContext} in their API functions which will hold the + * context in which they are called - essentially a representation of the + * computer they were called form. This context can be used to get a component + * in the computer (e.g. passed as another parameter) and to send signals to + * the computer. + * + * Do not implement this interface directly; use the {@see IItemDriver} and + * {@see IBlockDriver} interfaces for the respective component types. */ trait IDriver { /** @@ -15,49 +27,100 @@ trait IDriver { * This is used to allow computer programs to check the type of a component. * Components attached to a computer are identified by a number. This value * is returned when the type for such an ID is requested, so it should be - * unique for each driver. + * unique for each driver. For example, when a component is installed and the + * install signal was sent, the Lua code may check the type of the installed + * component using
drivers.component
. The returned string will
+ * be this one.
*/
def componentName: String
/**
- * The name of the API this component exposes to computers.
+ * The name of the API this component exposes to computers, if any.
*
* The component may return null or an empty string if it does not wish to
* define an API. If this is the case, we will not look for methods marked
* with the {@link Callback} annotation.
*
+ * This is the name of the table made available in the global drivers table,
+ * for example if this were to return 'disk', then the API will be available
+ * in Lua via drivers.disk
.
+ *
* This should be unique for individual component types. If not, functions
* in that API table may block another component's API from being installed
* properly: existing entries are not overwritten.
*
+ * Note that this must not be 'component', since that is reserved
+ * for a function that returns the type of an installed component given its
+ * ID.
+ *
* @return the name of the API made available to Lua.
*/
- def apiName: String
+ def apiName: String = null
/**
* Some initialization code that is run when the driver is installed.
*
* This is run after the driver's API table has been installed. It is loaded
- * into the Lua state in a temporary environment that has access to the
- * globals table and is discarded after the script has run.
+ * into the Lua state and run in the global, un-sandboxed environment. This
+ * means your scripts can mess things up bad, so make sure you know what
+ * you're doing and exposing.
*
- * This can be null or an empty string to do nothing. Otherwise this is
- * expected to be valid Lua code.
+ * This can be null to do nothing. Otherwise this is expected to be valid Lua
+ * code (it is simply loaded via load()
and then executed).
+ *
+ * The stream has to be recreated each time this is called. Normally you will
+ * return something along the lines of
+ * Mod.class.getResourceAsStream("/assets/mod/lua/ocapi.lua")
+ * from this method. If you wish to hard-code the returned script, you can
+ * use new ByteArrayInputStream(yourScript.getBytes())
instead.
+ * Note that the stream will automatically closed.
*
* @return the Lua code to run after installing the API table.
*/
- def apiCode: String
+ def apiCode: InputStream = null
+
+ /**
+ * This is called when a component is added to a computer.
+ *
+ * This happens if either of the following takes place:
+ * - The component is an item component and added in the computer.
+ * - The component is a block component and placed next to the computer.
+ * - The component is already in / next to the computer and the computer was
+ * off and is now starting up again. In this case this is called before the
+ * computer thread is started.
+ *
+ * You can use this to initialize some internal state or send some signal(s)
+ * to the computer. For example, graphics cards will scan for unbound
+ * monitors and automatically bind to the first free one.
+ *
+ * This is called before the install signal is sent to the computer.
+ *
+ * @param computer the computer to which the component is being added.
+ * @param component a handle to the component, as it was provided by the
+ * driver in its {@link IBlockDriver#component}/{@link IItemDriver#component}
+ * function.
+ */
+ def onInstall(computer: IComputerContext, component: Any) = {}
/**
* This is called when a component is removed from a computer.
*
+ * This happens if either of the following takes place:
+ * - The component is an item component and removed from the computer.
+ * - The component is a block component and broken or the computer is broken.
+ * - The component is already in / next to the computer and the computer was
+ * on and is now shutting down.
+ *
* The component should remove all handles it currently provides to the
* computer. For example, it should close any open files if it provides some
* form of file system.
*
+ * This is called before the uninstall signal is sent to the computer.
+ *
+ * @param computer the computer from which the component is being removed.
* @param component a handle to the component, as it was provided by the
- * driver in its {@link IBlockDriver#getComponent} or
- * {@link IItemDriver#getComponent} function.
+ * driver in its {@link IBlockDriver#component}/{@link IItemDriver#component}
+ * function.
*/
- def close(component: Object)
+ def onUninstall(computer: IComputerContext, component: Any) = {}
}
\ No newline at end of file
diff --git a/li/cil/oc/api/IItemDriver.scala b/li/cil/oc/api/IItemDriver.scala
index c5582b0c3..2df47cc42 100644
--- a/li/cil/oc/api/IItemDriver.scala
+++ b/li/cil/oc/api/IItemDriver.scala
@@ -9,42 +9,65 @@ import net.minecraft.item.ItemStack
* inserted into computers. An example for this are internal drives, memory
* and power supply units.
*
- * When an item component is added to a computer, the computer's OS will be
- * notified via a signal so that it may install the component's driver, for
- * example. After that the OS may start to interact with the component via the
- * API functions it provides.
+ * When trying to add an item to a computer the list of registered drivers is
+ * queried using the drivers' {@see #worksWith} functions. The first driver
+ * that replies positively and whose check against the slot type is successful,
+ * i.e. for which the {@see #componentType} matches the slot, will be used as
+ * the component's driver and the component will be installed. If no driver is
+ * found the item will be rejected and cannot be installed.
+ *
+ * The computer will store a list of installed components, the values of which
+ * are based on what the driver returns from its {@see #component} function
+ * at the point of time the component is installed.
+ * If a driver's API function queries a component via the context using
+ * {@see IComputerContext#component()} the returned value will be exactly that.
+ *
+ * Note that it is possible to write one driver that supports as many different
+ * items as you wish. I'd recommend writing one per device (type), though, to
+ * keep things modular and the {@see IDriver#componentName} more meaningful.
*/
trait IItemDriver extends IDriver {
/**
- * The component type of this item component.
+ * Used to determine the item types this driver handles.
*
- * This is used to determine into which slot of a computer this component may
- * go.
+ * This is used to determine which driver to use for an item when installed
+ * in a computer. Note that the return value should not change over time; if
+ * it does, though, an already installed component will not be ejected, since
+ * this value is only checked when adding components.
*
- * @return the component type.
+ * @param item the item to check.
+ * @return true if the item is supported; false otherwise.
*/
- def componentType: ComponentType.Value
+ def worksWith(item: ItemStack): Boolean
/**
- * The type of item this driver handles.
+ * The component type of the specified item this driver supports.
*
- * When an item is added into a computer and has this type, this driver will
- * be used for the block. The return value must not change over the lifetime
- * of this driver.
+ * This is used to determine into which slot of a computer the components
+ * this driver supports may go. This will only be called if a previous call
+ * to {@see #worksWith} with the same item type returned true.
*
- * @return the item type this driver is used for.
+ * @return the component type of the specified item.
*/
- def itemType: ItemStack
+ def componentType(item: ItemStack): ComponentType.Value
/**
- * Get a reference to the actual component.
+ * Gets a reference to the actual component.
*
- * This is used to provide context to the driver's methods, for example
- * when an API method is called this will always be passed as the first
- * parameter. It is also passed to the {@link IDriver#close} method.
+ * It is called once when a component is installed in a computer. At that
+ * time, the component will be assigned a unique ID by which it is referred
+ * to by from the Lua side. The computer keeps track of the mapping from ID
+ * to the actual component, which will be the value returned from this.
+ *
+ * This is used to provide context to the driver's API methods. The driver
+ * may get a reference to a component via the ID passed from a Lua program,
+ * and act accordingly (for that it must also have a context parameter, see
+ * the general interface documentation).
+ *
+ * This value also passed to the driver's {@link IDriver#close} method.
*
* @param item the item instance for which to get the component.
* @return the item component for that item, controlled by this driver.
*/
- def getComponent(item: ItemStack): Object
+ def component(item: ItemStack): Any
}
\ No newline at end of file
diff --git a/li/cil/oc/api/OpenComputersAPI.scala b/li/cil/oc/api/OpenComputersAPI.scala
index 610324a0d..80e371349 100644
--- a/li/cil/oc/api/OpenComputersAPI.scala
+++ b/li/cil/oc/api/OpenComputersAPI.scala
@@ -5,11 +5,11 @@ import li.cil.oc.server.computer.Drivers
object OpenComputersAPI {
def addDriver(driver: IBlockDriver) {
// TODO Use reflection to allow distributing the API.
- Drivers.addDriver(driver)
+ Drivers.add(driver)
}
def addDriver(driver: IItemDriver) {
// TODO Use reflection to allow distributing the API.
- Drivers.addDriver(driver)
+ Drivers.add(driver)
}
}
\ No newline at end of file
diff --git a/li/cil/oc/client/computer/Computer.scala b/li/cil/oc/client/computer/Computer.scala
index 25bd271a8..a7b6237b3 100644
--- a/li/cil/oc/client/computer/Computer.scala
+++ b/li/cil/oc/client/computer/Computer.scala
@@ -1,19 +1,44 @@
package li.cil.oc.client.computer
-import li.cil.oc.common.computer.IInternalComputerContext
+import scala.reflect.runtime.universe._
+
+import li.cil.oc.api.IComputerContext
+import li.cil.oc.common.computer.IComputer
+import net.minecraft.block.Block
+import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
-class Computer(val owner: AnyRef) extends IInternalComputerContext {
- def luaState = null
+/**
+ * This is a dummy class for the client side. It does nothing, really, just
+ * saves us a couple of side checks.
+ */
+class Computer(val owner: AnyRef) extends IComputerContext with IComputer {
+ // ----------------------------------------------------------------------- //
+ // IComputerContext
+ // ----------------------------------------------------------------------- //
+
+ def world = throw new NotImplementedError
+
+ def signal(name: String, args: Any*) = throw new NotImplementedError
+
+ def component[T: TypeTag](id: Int) = throw new NotImplementedError
+
+ // ----------------------------------------------------------------------- //
+ // IComputer
+ // ----------------------------------------------------------------------- //
+
+ def add(item: ItemStack, id: Int) = None
+
+ def add(block: Block, x: Int, y: Int, z: Int, id: Int) = None
+
+ def remove(id: Int) = false
def start() = false
-
+
def stop() {}
def update() {}
- def signal(name: String, args: Any*) {}
-
def readFromNBT(nbt: NBTTagCompound) {}
def writeToNBT(nbt: NBTTagCompound) {}
diff --git a/li/cil/oc/common/CommonProxy.scala b/li/cil/oc/common/CommonProxy.scala
index ba8af09f0..965d7969d 100644
--- a/li/cil/oc/common/CommonProxy.scala
+++ b/li/cil/oc/common/CommonProxy.scala
@@ -8,6 +8,7 @@ import li.cil.oc.Blocks
import li.cil.oc.Config
import li.cil.oc.Items
import li.cil.oc.server.computer.Computer
+import li.cil.oc.server.computer.Drivers
class CommonProxy {
def preInit(e: FMLPreInitializationEvent): Unit = {
@@ -30,5 +31,10 @@ class CommonProxy {
new Computer(null)
}
- def postInit(e: FMLPostInitializationEvent): Unit = {}
+ def postInit(e: FMLPostInitializationEvent): Unit = {
+ // Lock the driver registry to avoid drivers being added after computers
+ // may have already started up. This makes sure the driver API won't change
+ // over the course of a game, since that could lead to weird effects.
+ Drivers.locked = true
+ }
}
\ No newline at end of file
diff --git a/li/cil/oc/common/computer/IInternalComputerContext.scala b/li/cil/oc/common/computer/IInternalComputerContext.scala
deleted file mode 100644
index 717acf556..000000000
--- a/li/cil/oc/common/computer/IInternalComputerContext.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package li.cil.oc.common.computer
-
-import scala.collection.Seq
-import net.minecraft.nbt.NBTTagCompound
-import li.cil.oc.server.computer.IComputerContext
-import com.naef.jnlua.LuaState
-
-trait IInternalComputerContext extends IComputerContext {
- def luaState: LuaState
-
- def start(): Boolean
-
- def stop(): Unit
-
- def update()
-
- def readFromNBT(nbt: NBTTagCompound)
-
- def writeToNBT(nbt: NBTTagCompound)
-}
\ No newline at end of file
diff --git a/li/cil/oc/common/tileentity/TileEntityComputer.scala b/li/cil/oc/common/tileentity/TileEntityComputer.scala
index 87db03d52..8eafe8f86 100644
--- a/li/cil/oc/common/tileentity/TileEntityComputer.scala
+++ b/li/cil/oc/common/tileentity/TileEntityComputer.scala
@@ -34,7 +34,6 @@ class TileEntityComputer(isClient: Boolean) extends TileEntity with IComputerEnv
}
override def writeToNBT(nbt: NBTTagCompound) = {
- println("SAVING")
super.writeToNBT(nbt)
computer.writeToNBT(nbt)
}
@@ -52,14 +51,12 @@ class TileEntityComputer(isClient: Boolean) extends TileEntity with IComputerEnv
@ForgeSubscribe
def onChunkUnload(e: ChunkEvent.Unload) = {
- println("CHUNK UNLOADING")
MinecraftForge.EVENT_BUS.unregister(this)
computer.stop()
}
@ForgeSubscribe
def onWorldUnload(e: WorldEvent.Unload) = {
- println("WORLD UNLOADING")
MinecraftForge.EVENT_BUS.unregister(this)
computer.stop()
}
diff --git a/li/cil/oc/server/computer/Computer.scala b/li/cil/oc/server/computer/Computer.scala
index ec2f1e589..7db270699 100644
--- a/li/cil/oc/server/computer/Computer.scala
+++ b/li/cil/oc/server/computer/Computer.scala
@@ -2,22 +2,58 @@ package li.cil.oc.server.computer
import java.util.concurrent._
import java.util.concurrent.atomic.AtomicInteger
-
import scala.Array.canBuildFrom
import scala.collection.JavaConversions._
+import scala.collection.mutable._
+import scala.reflect.runtime.universe._
import scala.util.Random
-
import com.naef.jnlua._
-
import li.cil.oc.Config
-import li.cil.oc.common.computer.IInternalComputerContext
+import li.cil.oc.api.IComputerContext
+import li.cil.oc.common.computer.IComputer
+import net.minecraft.block.Block
+import net.minecraft.item.ItemStack
import net.minecraft.nbt._
+import java.util.Date
+import java.util.Calendar
+import java.util.Locale
-class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext with Runnable {
+/**
+ * Wrapper class for Lua states set up to behave like a pseudo-OS.
+ *
+ * This class takes care of the following:
+ * - Creating a new Lua state when started from a previously stopped state.
+ * - Updating the Lua state in a parallel thread so as not to block the game.
+ * - Managing a list of installed components and their drivers.
+ * - Synchronizing calls from the computer thread to drivers.
+ * - Saving the internal state of the computer across chunk saves/loads.
+ * - Closing the Lua state when stopping a previously running computer.
+ *
+ * See {@see Driver} to read more about component drivers and how they interact
+ * with computers - and through them the components they interface.
+ */
+class Computer(val owner: IComputerEnvironment) extends IComputerContext with IComputer with Runnable {
// ----------------------------------------------------------------------- //
// General
// ----------------------------------------------------------------------- //
+ /**
+ * This is the list of components currently attached to the computer. It is
+ * updated whenever a component is placed into the computer or removed from
+ * it, as well as when a block component is placed next to it or removed.
+ *
+ * On the Lua side we only refer to components by an ID, which is the array
+ * index for the component array. Drivers may fetch the underlying component
+ * object via the IComputerContext.
+ *
+ * Since the Lua program may query a component's type from its own thread we
+ * have to synchronize access to the component map. Note that there's a
+ * chance the Lua programs may see a component before its install signal has
+ * been processed, but I can't think of a scenario where this could become
+ * a real problem.
+ */
+ private val components = new HashMap[Int, (Any, Driver)] with SynchronizedMap[Int, (Any, Driver)]
+
/**
* The current execution state of the computer. This is used to track how to
* resume the computers main thread, if at all, and whether to accept new
@@ -26,7 +62,7 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
private var state = State.Stopped
/** The internal Lua state. Only set while the computer is running. */
- private var lua: LuaState = null
+ private[computer] var lua: LuaState = null
/**
* The base memory consumption of the kernel. Used to permit a fixed base
@@ -42,7 +78,7 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
* means to communicate actively with the computer (passively only drivers
* can interact with the computer by providing API functions).
*/
- private val signals = new LinkedBlockingQueue[Signal](256)
+ private val signals = new LinkedBlockingQueue[Signal](100)
// ----------------------------------------------------------------------- //
@@ -82,22 +118,61 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
private val saveMonitor = new Object()
// ----------------------------------------------------------------------- //
- // State
+ // IComputerContext
// ----------------------------------------------------------------------- //
- /** Starts asynchronous execution of this computer if it isn't running. */
- def start(): Boolean = stateMonitor.synchronized(
+ def world = owner.world
+
+ def signal(name: String, args: Any*) = {
+ args.foreach {
+ case null | _: Byte | _: Char | _: Short | _: Int | _: Long | _: Float | _: Double | _: String => Unit
+ case _ => throw new IllegalArgumentException()
+ }
+ stateMonitor.synchronized(state match {
+ // We don't push new signals when stopped or shutting down.
+ case State.Stopped | State.Stopping => false
+ // Currently sleeping. Cancel that and start immediately.
+ case State.Sleeping =>
+ future.cancel(true)
+ state = State.Suspended
+ signals.offer(new Signal(name, args.toArray))
+ future = Executor.pool.submit(this)
+ true
+ // Running or in driver call or only a short yield, just push the signal.
+ case _ =>
+ signals.offer(new Signal(name, args.toArray))
+ true
+ })
+ }
+
+ def component[T: TypeTag](id: Int) = components.get(id) match {
+ case None => throw new IllegalArgumentException("no such component")
+ case Some(component) =>
+ if (component.getClass() == typeOf[T]) component.asInstanceOf[T]
+ else throw new IllegalArgumentException("bad component type")
+ }
+
+ // ----------------------------------------------------------------------- //
+ // IComputer
+ // ----------------------------------------------------------------------- //
+
+ def start() = stateMonitor.synchronized(
state == State.Stopped && init() && {
state = State.Suspended
// Inject a dummy signal so that real one don't get swallowed. This way
- // we can just ignore the parameters the first time the kernel is run.
+ // we can just ignore the parameters the first time the kernel is run
+ // and all actual signals will be read using coroutine.yield().
signal("dummy")
+
+ // Initialize any installed components.
+ for ((component, driver) <- components.values)
+ driver.instance.onInstall(this, component)
+
future = Executor.pool.submit(this)
true
})
- /** Stops a computer, possibly asynchronously. */
- def stop(): Unit = saveMonitor.synchronized(stateMonitor.synchronized {
+ def stop() = saveMonitor.synchronized(stateMonitor.synchronized {
if (state != State.Stopped) {
if (state != State.Running) {
// If the computer is not currently running we can simply close it,
@@ -118,12 +193,6 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
}
})
- // ----------------------------------------------------------------------- //
- // IComputerContext
- // ----------------------------------------------------------------------- //
-
- def luaState = lua
-
def update() {
stateMonitor.synchronized(state match {
case State.Stopped | State.Stopping => return
@@ -152,34 +221,47 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
// Update world time for computer threads.
worldTime = owner.world.getWorldInfo().getWorldTotalTime()
- if (worldTime % 47 == 0) {
- signal("test", "ha!")
- }
-
// Update last time run to let our executor thread know it doesn't have to
- // pause, and wake it up if it did pause (because the game was paused).
+ // pause.
lastUpdate = System.currentTimeMillis
}
- def signal(name: String, args: Any*) = {
- args.foreach {
- case _: Byte | _: Short | _: Int | _: Long | _: Float | _: Double | _: String => Unit
- case _ => throw new IllegalArgumentException()
+ // ----------------------------------------------------------------------- //
+ // Note: driver interaction is synchronized, so we don't have to lock here.
+
+ def add(item: ItemStack, id: Int) =
+ Drivers.driverFor(item) match {
+ case None => None
+ case Some(driver) if !components.contains(id) =>
+ val component = driver.instance.component(item)
+ components += id -> (component, driver)
+ driver.instance.onInstall(this, component)
+ signal("component_install", id)
+ Some(driver.instance)
}
- stateMonitor.synchronized(state match {
- // We don't push new signals when stopped or shutting down.
- case State.Stopped | State.Stopping => /* Nothing. */
- // Currently sleeping. Cancel that and start immediately.
- case State.Sleeping =>
- future.cancel(true)
- state = State.Suspended
- signals.offer(new Signal(name, args.toArray))
- future = Executor.pool.submit(this)
- // Running or in driver call or only a short yield, just push the signal.
- case _ =>
- signals.offer(new Signal(name, args.toArray))
- })
- }
+
+ def add(block: Block, x: Int, y: Int, z: Int, id: Int) =
+ Drivers.driverFor(block) match {
+ case None => None
+ case Some(driver) if !components.contains(id) =>
+ val component = driver.instance.component(x, y, z)
+ components += id -> (component, driver)
+ driver.instance.onInstall(this, component)
+ signal("component_install", id)
+ Some(driver.instance)
+ }
+
+ def remove(id: Int) =
+ if (components.contains(id)) {
+ val (component, driver) = components(id)
+ components.remove(id)
+ driver.instance.onUninstall(this, component)
+ signal("component_uninstall", id)
+ true
+ }
+ else false
+
+ // ----------------------------------------------------------------------- //
def readFromNBT(nbt: NBTTagCompound): Unit =
saveMonitor.synchronized(this.synchronized {
@@ -347,7 +429,11 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
return false
}
- def init(): Boolean = {
+ // ----------------------------------------------------------------------- //
+ // Internals
+ // ----------------------------------------------------------------------- //
+
+ private def init(): Boolean = {
// Creates a new state with all base libraries and the persistence library
// loaded into it. This means the state has much more power than it
// rightfully should have, so we sandbox it a bit in the following.
@@ -359,18 +445,38 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
try {
// Push a couple of functions that override original Lua API functions or
- // that add new functionality to it.1)
- lua.getGlobal("os")
+ // that add new functionality to it.
+ lua.newTable()
- // Return ingame time for os.time().
+ // Set up driver information callbacks, i.e. to query for the presence of
+ // components with a given ID, and their type.
lua.pushJavaFunction(new JavaFunction() {
def invoke(lua: LuaState): Int = {
- // Minecraft starts days at 6 o'clock, so we add those six hours.
- lua.pushNumber(worldTime + 6000)
+ lua.pushBoolean(components.contains(lua.checkInteger(1)))
return 1
}
})
- lua.setField(-2, "time")
+ lua.setField(-2, "exists")
+
+ lua.pushJavaFunction(new JavaFunction() {
+ def invoke(lua: LuaState): Int = {
+ val id = lua.checkInteger(1)
+ components.get(id) match {
+ case None => lua.pushNil()
+ case Some((component, driver)) =>
+ lua.pushString(driver.instance.componentName)
+ }
+ return 1
+ }
+ })
+ lua.setField(-2, "type")
+
+ // "Pop" the components table.
+ lua.setGlobal("component")
+
+ // Push a couple of functions that override original Lua API functions or
+ // that add new functionality to it.
+ lua.getGlobal("os")
// Custom os.clock() implementation returning the time the computer has
// been running, instead of the native library...
@@ -385,6 +491,29 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
})
lua.setField(-2, "clock")
+ // Return ingame time for os.time().
+ lua.pushJavaFunction(new JavaFunction() {
+ def invoke(lua: LuaState): Int = {
+ // Game time is in ticks, so that each day has 24000 ticks, meaning
+ // one hour is game time divided by one thousand. Also, Minecraft
+ // starts days at 6 o'clock, so we add those six hours. Thus:
+ // timestamp = (time + 6000) / 1000[h] * 60[m] * 60[s] * 1000[ms]
+ lua.pushNumber((worldTime + 6000) * 60 * 60)
+ return 1
+ }
+ })
+ lua.setField(-2, "time")
+
+ // Date-time formatting using Java's formatting capabilities.
+ lua.pushJavaFunction(new JavaFunction() {
+ def invoke(lua: LuaState): Int = {
+ val calendar = Calendar.getInstance(Locale.ENGLISH);
+ calendar.setTimeInMillis(lua.checkInteger(1))
+ return 1
+ }
+ })
+ lua.setField(-2, "date")
+
// Custom os.difftime(). For most Lua implementations this would be the
// same anyway, but just to be on the safe side.
lua.pushJavaFunction(new JavaFunction() {
@@ -494,8 +623,8 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
// building a table of permanent values used when persisting/unpersisting
// the state.
lua.newTable()
- lua.setGlobal("drivers")
- Drivers.injectInto(this)
+ lua.setGlobal("driver")
+ Drivers.installOn(this)
// Run the boot script. This creates the global sandbox variable that is
// used as the environment for any processes the kernel spawns, adds a
@@ -538,9 +667,14 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
return false
}
- def close(): Unit = stateMonitor.synchronized(
+ private def close(): Unit = stateMonitor.synchronized(
if (state != State.Stopped) {
state = State.Stopped
+
+ // Shutdown any installed components.
+ for ((component, driver) <- components.values)
+ driver.instance.onUninstall(this, component)
+
lua.setTotalMemory(Integer.MAX_VALUE);
lua.close()
lua = null
@@ -595,15 +729,7 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
// Got a signal, inject it and call any handlers (if any).
case signal => {
lua.pushString(signal.name)
- signal.args.foreach {
- case arg: Byte => lua.pushInteger(arg)
- case arg: Short => lua.pushInteger(arg)
- case arg: Int => lua.pushInteger(arg)
- case arg: Long => lua.pushNumber(arg)
- case arg: Float => lua.pushNumber(arg)
- case arg: Double => lua.pushNumber(arg)
- case arg: String => lua.pushString(arg)
- }
+ signal.args.foreach(lua.pushJavaObject)
lua.resume(1, 1 + signal.args.length)
}
}
@@ -700,27 +826,27 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
/** The computer is paused and waiting for the game to resume. */
val DriverReturnPaused = Value("DriverReturnPaused")
}
-
- /** Singleton for requesting executors that run our Lua states. */
- private object Executor {
- val pool = Executors.newScheduledThreadPool(Config.threads,
- new ThreadFactory() {
- private val threadNumber = new AtomicInteger(1)
-
- private val group = System.getSecurityManager() match {
- case null => Thread.currentThread().getThreadGroup()
- case s => s.getThreadGroup()
- }
-
- def newThread(r: Runnable): Thread = {
- val name = "OpenComputers-" + threadNumber.getAndIncrement()
- val thread = new Thread(group, r, name)
- if (!thread.isDaemon())
- thread.setDaemon(true)
- if (thread.getPriority() != Thread.MIN_PRIORITY)
- thread.setPriority(Thread.MIN_PRIORITY)
- return thread
- }
- })
- }
}
+
+/** Singleton for requesting executors that run our Lua states. */
+private[computer] object Executor {
+ val pool = Executors.newScheduledThreadPool(Config.threads,
+ new ThreadFactory() {
+ private val threadNumber = new AtomicInteger(1)
+
+ private val group = System.getSecurityManager() match {
+ case null => Thread.currentThread().getThreadGroup()
+ case s => s.getThreadGroup()
+ }
+
+ def newThread(r: Runnable): Thread = {
+ val name = "OpenComputers-" + threadNumber.getAndIncrement()
+ val thread = new Thread(group, r, name)
+ if (!thread.isDaemon())
+ thread.setDaemon(true)
+ if (thread.getPriority() != Thread.MIN_PRIORITY)
+ thread.setPriority(Thread.MIN_PRIORITY)
+ return thread
+ }
+ })
+}
\ No newline at end of file
diff --git a/li/cil/oc/server/computer/Driver.scala b/li/cil/oc/server/computer/Driver.scala
index 1c94fa814..2d814431f 100644
--- a/li/cil/oc/server/computer/Driver.scala
+++ b/li/cil/oc/server/computer/Driver.scala
@@ -1,23 +1,43 @@
package li.cil.oc.server.computer
import java.lang.reflect.Method
-
+import scala.compat.Platform.EOL
+import scala.reflect.runtime.universe._
import com.naef.jnlua.JavaFunction
+import com.naef.jnlua.LuaRuntimeException
import com.naef.jnlua.LuaState
+import li.cil.oc.OpenComputers
+import li.cil.oc.api._
-import li.cil.oc.api.Callback
-import li.cil.oc.api.IDriver
-import li.cil.oc.common.computer.IInternalComputerContext
+class ItemDriver(val instance: IItemDriver) extends Driver
+class BlockDriver(val instance: IBlockDriver) extends Driver
-private[oc] class Driver(val driver: IDriver) {
- def injectInto(context: IInternalComputerContext) {
+/**
+ * Wrapper for external drivers.
+ *
+ * We create one instance per registered driver of this. It is used to inject
+ * the API the driver offers into the computer, and, in particular, for
+ * generating the wrappers for the API functions, which are closures with the
+ * computer the API was installed into to provide context should a function
+ * require it.
+ */
+abstract private[oc] class Driver {
+ /** The actual driver as registered via the Drivers registry. */
+ def instance: IDriver
+
+ /** Installs this driver's API on the specified computer. */
+ def installOn(computer: Computer) {
// Check if the component actually provides an API.
- val api = driver.apiName
+ val api = instance.apiName
if (api == null || api.isEmpty()) return
- val lua = context.luaState
+ if (api.equals("component")) {
+ OpenComputers.log.warning("Trying to register API with reserved name 'component'.")
+ return
+ }
+ val lua = computer.lua
// Get or create table holding API tables.
- lua.getGlobal("drivers") // ... drivers?
+ lua.getGlobal("driver") // ... drivers?
assert(!lua.isNil(-1)) // ... drivers
// Get or create API table.
@@ -29,95 +49,125 @@ private[oc] class Driver(val driver: IDriver) {
lua.setField(-3, api) // ... drivers api
} // ... drivers api
- for (method <- driver.getClass().getMethods())
- method.getAnnotation(classOf[Callback]) match {
- case null => Unit // No annotation.
- case annotation => {
+ val mirror = runtimeMirror(instance.getClass.getClassLoader)
+ val instanceMirror = mirror.reflect(instance)
+ val instanceType = instanceMirror.symbol.typeSignature
+ for (method <- instanceType.members collect { case m if m.isMethod => m.asMethod })
+ method.annotations collect {
+ case annotation: Callback => {
val name = annotation.name
lua.getField(-1, name) // ... drivers api func?
if (lua.isNil(-1)) { // ... drivers api nil
// No such entry yet.
lua.pop(1) // ... drivers api
- lua.pushJavaFunction(new MethodWrapper(context, method)) // ... drivers api func
+ lua.pushJavaFunction(new APIClosure(mirror, instanceMirror.reflectMethod(method), computer)) // ... drivers api func
lua.setField(-2, name) // ... drivers api
}
else { // ... drivers api func
// Entry already exists, skip it.
lua.pop(1) // ... drivers api
- // TODO Log warning properly via a logger.
- println("WARNING: Duplicate API entry, ignoring: " + api + "." + name)
+ // Note that we can be sure it's an issue with two drivers
+ // colliding, because this is guaranteed to run before any user
+ // code had a chance to mess with the table.
+ OpenComputers.log.warning(String.format(
+ "Duplicate API entry, ignoring %s.%s of driver %s.",
+ api, name, instance.componentName))
}
}
} // ... drivers api
lua.pop(2) // ...
- }
- private class MethodWrapper(val context: IInternalComputerContext, val method: Method) extends JavaFunction {
- def invoke(state: LuaState): Int = {
- return 0
+ // Run custom init script.
+ val apiCode = instance.apiCode
+ if (apiCode != null) {
+ try {
+ lua.load(apiCode, instance.apiName, "t") // ... func
+ apiCode.close()
+ lua.call(0, 0) // ...
+ }
+ catch {
+ case e: LuaRuntimeException =>
+ OpenComputers.log.warning(String.format(
+ "Initialization code of driver %s threw an error: %s",
+ instance.componentName, e.getLuaStackTrace.mkString("", EOL, EOL)))
+ case e: Throwable =>
+ OpenComputers.log.warning(String.format(
+ "Initialization code of driver %s threw an error: %s",
+ instance.componentName, e.getStackTraceString))
+ }
}
}
- /*
- private class MethodWrapper(val context: IInternalComputerContext, val method: Method) extends JavaFunction {
- private val classOfBoolean = classOf[Boolean]
- private val classOfByte = classOf[Byte]
- private val classOfShort = classOf[Short]
- private val classOfInteger = classOf[Int]
- private val classOfLong = classOf[Long]
- private val classOfFloat = classOf[Float]
- private val classOfDouble = classOf[Double]
- private val classOfString = classOf[String]
- private val parameterTypes = method.getParameterTypes.zipWithIndex
- private val parameterCount = parameterTypes.size
- private val returnType = method.getReturnType
- private val returnsTuple = returnType.isInstanceOf[Array[Object]]
- private val returnsNothing = returnType.equals(Void.TYPE)
+ /**
+ * This class is used to represent closures for driver API callbacks.
+ *
+ * It stores the computer it is used by, to allow passing it along as the
+ * {@see IComputerContext} for callbacks if they specify it as a parameter,
+ * and for interacting with the Lua state to pull parameters and push results
+ * returned from the callback.
+ */
+ private class APIClosure(mirror: Mirror, val method: MethodMirror, val computer: Computer) extends JavaFunction {
+ /**
+ * Based on the method's parameters we build a list of transformations
+ * that convert Lua input to the expected parameter type.
+ */
+ val parameterTransformations = buildParameterTransformations(mirror, method.symbol)
- // TODO Rework all of this, most likely won't work because of type erasure.
- def invoke(state: LuaState): Int = {
- // Parse the parameters, convert them to Java types.
- val parameters = Array(context) ++ parameterTypes.map {
- //case (classOfBoolean, i) => boolean2Boolean(state.checkBoolean(i + 1))
- case (classOfByte, i) => java.lang.Byte.valueOf(state.checkInteger(i + 1).toByte)
- case (classOfShort, i) => java.lang.Short.valueOf(state.checkInteger(i + 1).toShort)
- case (classOfInteger, i) => java.lang.Integer.valueOf(state.checkInteger(i + 1))
- case (classOfLong, i) => java.lang.Long.valueOf(state.checkInteger(i + 1).toLong)
- case (classOfFloat, i) => java.lang.Float.valueOf(state.checkNumber(i + 1).toFloat)
- case (classOfDouble, i) => java.lang.Double.valueOf(state.checkNumber(i + 1))
- case (classOfString, i) => state.checkString(i + 1)
- case _ => null
- }
+ /**
+ * Based on the method's return value we build a callback that will push
+ * that result to the stack, if any, and return the number of pushed values.
+ */
+ val returnTransformation = buildReturnTransformation(method.symbol)
- // Call the actual function, grab the result, if any.
- val result = call(parameters: _*)
-
- // Check the result, convert it to Lua.
- if (returnsTuple) {
- val array = result.asInstanceOf[Array[Object]]
- array.foreach(v => push(state, v, v.getClass()))
- return array.length
- }
- else if (returnsNothing) {
- return 0
- }
- else {
- push(state, result, returnType)
- return 1
- }
- }
-
- private def push(state: LuaState, value: Object, clazz: Class[_]) = clazz match {
- case classOfBoolean => state.pushBoolean(value.asInstanceOf[Boolean])
- case classOfInteger => state.pushNumber(value.asInstanceOf[Int])
- case classOfDouble => state.pushNumber(value.asInstanceOf[Double])
- case classOfString => state.pushString(value.asInstanceOf[String])
- case _ => state.pushNil()
- }
-
- protected def call(args: AnyRef*) = {
- method.invoke(driver, args)
+ /** This is the function actually called from the Lua state. */
+ def invoke(lua: LuaState): Int = {
+ return returnTransformation(computer,
+ method(parameterTransformations.map(_(computer)): _*))
}
}
-*/
+
+ /**
+ * This generates the transformation functions that are used to convert the
+ * Lua parameters to Java types to be passed on to the API function.
+ */
+ private def buildParameterTransformations(mirror: Mirror, method: MethodSymbol): Array[Computer => Any] = {
+ // No parameters?
+ if (method.paramss.length == 0 || method.paramss(0).length == 0) {
+ return Array()
+ }
+
+ val params = method.paramss(0)
+
+ // Do we have a callback function that wants to handle its arguments
+ // manually? If so, convert all arguments and pack them into an array.
+ // TODO test if this really works.
+ if (params.length == 2 &&
+ params(0) == typeOf[IComputerContext].typeSymbol &&
+ params(1) == typeOf[Array[Any]].typeSymbol) {
+ Array(
+ (c: Computer) => c,
+ (c: Computer) => (1 to c.lua.getTop()).map(
+ c.lua.toJavaObject(_, classOf[Object])))
+ }
+ // Otherwise build converters based on the method's signature.
+ else params.zipWithIndex.map {
+ case (t, i) if t == typeOf[IComputerContext].typeSymbol => (c: Computer) => c
+ case (t, i) => {
+ val clazz = mirror.runtimeClass(t.asClass)
+ (c: Computer) => c.lua.toJavaObject(i, clazz)
+ }
+ }.toArray
+ }
+
+ /**
+ * This generates the transformation function that is used to convert the
+ * return value of an API function to a Lua type and push it onto the stack,
+ * returning the number of values pushed. We need that number in case we
+ * return a tuple (i.e. the function returned an array).
+ */
+ private def buildReturnTransformation(method: MethodSymbol): (Computer, Any) => Int =
+ method.returnType match {
+ case t if t == Unit => (c, v) => 0
+ case t => (c, v) => c.lua.pushJavaObject(v); 1
+ }
}
\ No newline at end of file
diff --git a/li/cil/oc/server/computer/Drivers.scala b/li/cil/oc/server/computer/Drivers.scala
index 8927c3dc9..4bbe30198 100644
--- a/li/cil/oc/server/computer/Drivers.scala
+++ b/li/cil/oc/server/computer/Drivers.scala
@@ -1,37 +1,48 @@
package li.cil.oc.server.computer
-import scala.collection.mutable.Map
+import scala.collection.mutable.ArrayBuffer
import li.cil.oc.api.IBlockDriver
import li.cil.oc.api.IItemDriver
-import li.cil.oc.common.computer.IInternalComputerContext
import net.minecraft.block.Block
import net.minecraft.item.ItemStack
/**
* This class keeps track of registered drivers and provides installation logic
- * for each registered component type.
+ * for each registered driver.
*
* Each component type must register its driver with this class to be used with
* computers, since this class is used to determine whether an object is a
* valid component or not.
+ *
+ * All drivers must be installed once the game starts - in the init phase - and
+ * are then injected into all computers started up past that point. A driver is
+ * a set of functions made available to the computer. These functions will
+ * usually require a component of the type the driver wraps to be installed in
+ * the computer, but may also provide context-free functions.
*/
private[oc] object Drivers {
- private val blocks = Map.empty[Int, Driver]
- private val items = Map.empty[Int, Driver]
+ /** The list of registered block drivers. */
+ private val blocks = ArrayBuffer.empty[BlockDriver]
+
+ /** The list of registered item drivers. */
+ private val items = ArrayBuffer.empty[ItemDriver]
+
+ /** Used to keep track of whether we're past the init phase. */
+ var locked = false
/**
* Registers a new driver for a block component.
*
* Whenever the neighboring blocks of a computer change, it checks if there
- * exists a driver for the changed block, and if so adds it to the list of
- * available components.
+ * exists a driver for the changed block, and if so installs it.
*
* @param driver the driver for that block type.
*/
- def addDriver(driver: IBlockDriver) {
- if (blocks.contains(driver.blockType.blockID)) return
- blocks += driver.blockType.blockID -> new Driver(driver)
+ def add(driver: IBlockDriver) {
+ if (locked) throw new IllegalStateException("Please register all drivers in the init phase.")
+ if (!blocks.exists(entry => entry.instance == driver))
+ blocks += new BlockDriver(driver)
}
/**
@@ -42,15 +53,34 @@ private[oc] object Drivers {
*
* @param driver the driver for that item type.
*/
- def addDriver(driver: IItemDriver) {
- if (items.contains(driver.itemType.itemID)) return
- items += driver.itemType.itemID -> new Driver(driver)
+ def add(driver: IItemDriver) {
+ if (locked) throw new IllegalStateException("Please register all drivers in the init phase.")
+ if (!blocks.exists(entry => entry.instance == driver))
+ items += new ItemDriver(driver)
}
- def getDriver(block: Block) = blocks(block.blockID)
+ /**
+ * Used when a new block is placed next to a computer to see if we have a
+ * driver for it. If we have one, we'll return it.
+ *
+ * @param block the type of block to check for a driver for.
+ * @return the driver for that block type if we have one.
+ */
+ def driverFor(block: Block) = blocks.find(_.instance.worksWith(block))
- def getDriver(item: ItemStack) = blocks(item.itemID)
+ /**
+ * Used when an item component is added to a computer to see if we have a
+ * driver for it. If we have one, we'll return it.
+ *
+ * @param item the type of item to check for a driver for.
+ * @return the driver for that item type if we have one.
+ */
+ def driverFor(item: ItemStack) = items.find(_.instance.worksWith(item))
- def injectInto(context: IInternalComputerContext) =
- (blocks.values ++ items.values).foreach(_.injectInto(context))
+ /**
+ * Used by the computer to initialize its Lua state, injecting the APIs of
+ * all known drivers.
+ */
+ private[computer] def installOn(computer: Computer) =
+ (blocks ++ items).foreach(_.installOn(computer))
}
diff --git a/li/cil/oc/server/computer/IComputerContext.scala b/li/cil/oc/server/computer/IComputerContext.scala
deleted file mode 100644
index 1db5cea9d..000000000
--- a/li/cil/oc/server/computer/IComputerContext.scala
+++ /dev/null
@@ -1,9 +0,0 @@
-package li.cil.oc.server.computer
-
-import com.naef.jnlua.LuaState
-
-import net.minecraft.nbt.NBTTagCompound
-
-trait IComputerContext {
- def signal(name: String, args: Any*)
-}
\ No newline at end of file
diff --git a/li/cil/oc/server/drivers/HDD.scala b/li/cil/oc/server/drivers/HDD.scala
index 1382d6e12..455137cbd 100644
--- a/li/cil/oc/server/drivers/HDD.scala
+++ b/li/cil/oc/server/drivers/HDD.scala
@@ -1,37 +1,35 @@
package li.cil.oc.server.drivers
-import li.cil.oc.Items
+import li.cil.oc.Config
+import li.cil.oc.api.Callback
import li.cil.oc.api.ComponentType
import li.cil.oc.api.IItemDriver
import li.cil.oc.server.components.Disk
import net.minecraft.item.ItemStack
-import li.cil.oc.api.Callback
object HDDDriver extends IItemDriver {
def componentName = "disk"
- def apiName = "disk"
-
- def apiCode = null
+ override def apiName = "disk"
@Callback(name = "mount")
- def mount(component: Object, path: String) {
+ def mount(component: Any, path: String) {
}
- def componentType = ComponentType.HDD
+ def worksWith(item: ItemStack) = item.itemID == Config.itemHDDId
- def itemType = new ItemStack(Items.hdd)
+ def componentType(item: ItemStack) = ComponentType.HDD
- def getComponent(item: ItemStack) = new HDDComponent(item)
+ def component(item: ItemStack) = new HDDComponent(item)
- def close(component: Object) {
+ def close(component: Any) {
component.asInstanceOf[HDDComponent].close()
}
}
class HDDComponent(val item: ItemStack) {
val disk = new Disk()
-
+
def close() = disk.close()
}
\ No newline at end of file