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