working on drivers

This commit is contained in:
Florian Nücke 2013-09-10 17:26:25 +02:00
parent baad2ebbda
commit f9cd7fd3b6
20 changed files with 629 additions and 292 deletions

View File

@ -58,7 +58,7 @@ do
end end
end end
end end
wrapRecursive(drivers) wrapRecursive(driver)
end end
--[[ Permanent value tables. --[[ Permanent value tables.

View File

@ -72,6 +72,8 @@ local function buildSandbox()
yield = coroutine.yield yield = coroutine.yield
}, },
driver = driver,
math = { math = {
abs = math.abs, abs = math.abs,
acos = math.acos, acos = math.acos,
@ -139,6 +141,8 @@ local function buildSandbox()
unpack = table.unpack unpack = table.unpack
} }
} }
-- Make the sandbox its own globals table.
sandbox._G = sandbox sandbox._G = sandbox
-- Allow sandboxes to load code, but only in text form, and in the sandbox. -- Allow sandboxes to load code, but only in text form, and in the sandbox.

View File

@ -750,7 +750,7 @@ public class LuaState {
*/ */
public synchronized void pushJavaObject(Object object) { public synchronized void pushJavaObject(Object object) {
check(); 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)); 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 * 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 * string convertible to a number. If so, the argument value is returned as

View File

@ -5,6 +5,7 @@ object Config {
var blockMonitorId = 3651 var blockMonitorId = 3651
var itemHDDId = 4600 var itemHDDId = 4600
var itemGPUId = 4601
var threads = 4 var threads = 4
} }

View File

@ -1,5 +1,7 @@
package li.cil.oc package li.cil.oc
import java.util.logging.Logger
import cpw.mods.fml.common.Mod import cpw.mods.fml.common.Mod
import cpw.mods.fml.common.Mod.EventHandler import cpw.mods.fml.common.Mod.EventHandler
import cpw.mods.fml.common.SidedProxy 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") @Mod(modid = "OpenComputers", name = "OpenComputers", version = "0.0.0", dependencies = "required-after:Forge@[9.10.0.804,)", modLanguage = "scala")
@NetworkMod(clientSideRequired = true, serverSideRequired = false) @NetworkMod(clientSideRequired = true, serverSideRequired = false)
object OpenComputers { object OpenComputers {
/** Logger used all throughout this mod. */
val log = Logger.getLogger("OpenComputers")
@SidedProxy( @SidedProxy(
clientSide = "li.cil.oc.client.ClientProxy", clientSide = "li.cil.oc.client.ClientProxy",
serverSide = "li.cil.oc.common.CommonProxy") serverSide = "li.cil.oc.common.CommonProxy")

View File

@ -6,29 +6,39 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* This annotation is used by components to mark methods that are part of the * This annotation is used by {@see IDriver}s to mark methods that are part of
* API they expose to computers. * the API they expose to computers.
* *
* If the component provides an API, each method annotated like this an entry in * If the driver provides an API, for each method annotated like this an entry
* the API table will be generated. The actual entry will be a wrapper that * 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 * 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 * Any type is supported as long as it can be converted by JNLua's {@see
* to boolean, number, number, and string respectively. If a function parameter * com.naef.jnlua.DefaultConverter}. However, so as not to break persistence
* is of an unsupported type its value will always be the type's default value. * (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
* <tt>Map</tt>,<tt>List</tt> and arrays should be passed along.
* *
* Supported return types are: Boolean, Integer, Double, String and Array. These * If you wish more flexibility in how you handle parameters, define the
* map to the same types as parameter types, with the exception of arrays which * callback with the following signature:
* 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 * <pre>
* ignored. * Object[] callback(IComputerContext, Object[])
* </pre>
*
* 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) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface Callback { 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(); String name();
} }

View File

@ -12,5 +12,4 @@ object ComponentType extends Enumeration {
val RAM = Value("RAM") val RAM = Value("RAM")
val HDD = Value("HDD") val HDD = Value("HDD")
val PCI = Value("PCI") val PCI = Value("PCI")
val GPU = Value("GPU")
} }

View File

@ -9,22 +9,34 @@ import net.minecraft.block.Block
* placed in the world, and particularly: next to computers. An example for * placed in the world, and particularly: next to computers. An example for
* this are external drives, monitors and modems. * this are external drives, monitors and modems.
* *
* When a block component is added next to a computer, the computer's OS will * When a block component is placed next to a computer, the list of registered
* be notified via a signal so that it may install the component's driver, for * drivers is queried using the drivers' {@see #worksWith} functions. The first
* example. After that the OS may start to interact with the component via the * driver that replies positively will be used as the component's driver and
* API functions it provides. * 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 { 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 * This is used to determine which driver to use for a block placed next to a
* will be used for the block. The return value must not change over the * computer. Note that the return value should not change over time; if it
* lifetime of this driver. * 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. * 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. * @param z the Z coordinate of the block to get the component for.
* @return the block component at that location, controlled by this driver. * @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
} }

View File

@ -1,5 +1,7 @@
package li.cil.oc.api package li.cil.oc.api
import java.io.InputStream
/** /**
* This interface specifies the structure of a driver for a component. * 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 * 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 * Lua state when the driver is installed, and provide general information used
* by the computer. * 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 { trait IDriver {
/** /**
@ -15,49 +27,100 @@ trait IDriver {
* This is used to allow computer programs to check the type of a component. * 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 * 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 * 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 <code>drivers.component</code>. The returned string will
* be this one.
*/ */
def componentName: String 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 * 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 * define an API. If this is the case, we will not look for methods marked
* with the {@link Callback} annotation. * 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 <code>drivers.disk</code>.
*
* This should be unique for individual component types. If not, functions * This should be unique for individual component types. If not, functions
* in that API table may block another component's API from being installed * in that API table may block another component's API from being installed
* properly: existing entries are not overwritten. * properly: existing entries are not overwritten.
* *
* Note that this <em>must not</em> 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. * @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. * 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 * 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 * into the Lua state and run in the global, un-sandboxed environment. This
* globals table and is discarded after the script has run. * 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 * This can be null to do nothing. Otherwise this is expected to be valid Lua
* expected to be valid Lua code. * code (it is simply loaded via <code>load()</code> and then executed).
*
* The stream has to be recreated each time this is called. Normally you will
* return something along the lines of
* <code>Mod.class.getResourceAsStream("/assets/mod/lua/ocapi.lua")</code>
* from this method. If you wish to hard-code the returned script, you can
* use <code>new ByteArrayInputStream(yourScript.getBytes())</code> instead.
* Note that the stream will automatically closed.
* *
* @return the Lua code to run after installing the API table. * @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 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 * The component should remove all handles it currently provides to the
* computer. For example, it should close any open files if it provides some * computer. For example, it should close any open files if it provides some
* form of file system. * 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 * @param component a handle to the component, as it was provided by the
* driver in its {@link IBlockDriver#getComponent} or * driver in its {@link IBlockDriver#component}/{@link IItemDriver#component}
* {@link IItemDriver#getComponent} function. * function.
*/ */
def close(component: Object) def onUninstall(computer: IComputerContext, component: Any) = {}
} }

View File

@ -9,42 +9,65 @@ import net.minecraft.item.ItemStack
* inserted into computers. An example for this are internal drives, memory * inserted into computers. An example for this are internal drives, memory
* and power supply units. * and power supply units.
* *
* When an item component is added to a computer, the computer's OS will be * When trying to add an item to a computer the list of registered drivers is
* notified via a signal so that it may install the component's driver, for * queried using the drivers' {@see #worksWith} functions. The first driver
* example. After that the OS may start to interact with the component via the * that replies positively and whose check against the slot type is successful,
* API functions it provides. * 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 { 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 * This is used to determine which driver to use for an item when installed
* go. * 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 * This is used to determine into which slot of a computer the components
* be used for the block. The return value must not change over the lifetime * this driver supports may go. This will only be called if a previous call
* of this driver. * 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 * It is called once when a component is installed in a computer. At that
* when an API method is called this will always be passed as the first * time, the component will be assigned a unique ID by which it is referred
* parameter. It is also passed to the {@link IDriver#close} method. * 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. * @param item the item instance for which to get the component.
* @return the item component for that item, controlled by this driver. * @return the item component for that item, controlled by this driver.
*/ */
def getComponent(item: ItemStack): Object def component(item: ItemStack): Any
} }

View File

@ -5,11 +5,11 @@ import li.cil.oc.server.computer.Drivers
object OpenComputersAPI { object OpenComputersAPI {
def addDriver(driver: IBlockDriver) { def addDriver(driver: IBlockDriver) {
// TODO Use reflection to allow distributing the API. // TODO Use reflection to allow distributing the API.
Drivers.addDriver(driver) Drivers.add(driver)
} }
def addDriver(driver: IItemDriver) { def addDriver(driver: IItemDriver) {
// TODO Use reflection to allow distributing the API. // TODO Use reflection to allow distributing the API.
Drivers.addDriver(driver) Drivers.add(driver)
} }
} }

View File

@ -1,10 +1,37 @@
package li.cil.oc.client.computer 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 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 start() = false
@ -12,8 +39,6 @@ class Computer(val owner: AnyRef) extends IInternalComputerContext {
def update() {} def update() {}
def signal(name: String, args: Any*) {}
def readFromNBT(nbt: NBTTagCompound) {} def readFromNBT(nbt: NBTTagCompound) {}
def writeToNBT(nbt: NBTTagCompound) {} def writeToNBT(nbt: NBTTagCompound) {}

View File

@ -8,6 +8,7 @@ import li.cil.oc.Blocks
import li.cil.oc.Config import li.cil.oc.Config
import li.cil.oc.Items import li.cil.oc.Items
import li.cil.oc.server.computer.Computer import li.cil.oc.server.computer.Computer
import li.cil.oc.server.computer.Drivers
class CommonProxy { class CommonProxy {
def preInit(e: FMLPreInitializationEvent): Unit = { def preInit(e: FMLPreInitializationEvent): Unit = {
@ -30,5 +31,10 @@ class CommonProxy {
new Computer(null) 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
}
} }

View File

@ -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)
}

View File

@ -34,7 +34,6 @@ class TileEntityComputer(isClient: Boolean) extends TileEntity with IComputerEnv
} }
override def writeToNBT(nbt: NBTTagCompound) = { override def writeToNBT(nbt: NBTTagCompound) = {
println("SAVING")
super.writeToNBT(nbt) super.writeToNBT(nbt)
computer.writeToNBT(nbt) computer.writeToNBT(nbt)
} }
@ -52,14 +51,12 @@ class TileEntityComputer(isClient: Boolean) extends TileEntity with IComputerEnv
@ForgeSubscribe @ForgeSubscribe
def onChunkUnload(e: ChunkEvent.Unload) = { def onChunkUnload(e: ChunkEvent.Unload) = {
println("CHUNK UNLOADING")
MinecraftForge.EVENT_BUS.unregister(this) MinecraftForge.EVENT_BUS.unregister(this)
computer.stop() computer.stop()
} }
@ForgeSubscribe @ForgeSubscribe
def onWorldUnload(e: WorldEvent.Unload) = { def onWorldUnload(e: WorldEvent.Unload) = {
println("WORLD UNLOADING")
MinecraftForge.EVENT_BUS.unregister(this) MinecraftForge.EVENT_BUS.unregister(this)
computer.stop() computer.stop()
} }

View File

@ -2,22 +2,58 @@ package li.cil.oc.server.computer
import java.util.concurrent._ import java.util.concurrent._
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import scala.Array.canBuildFrom import scala.Array.canBuildFrom
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
import scala.collection.mutable._
import scala.reflect.runtime.universe._
import scala.util.Random import scala.util.Random
import com.naef.jnlua._ import com.naef.jnlua._
import li.cil.oc.Config 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 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 // 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 * 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 * 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 private var state = State.Stopped
/** The internal Lua state. Only set while the computer is running. */ /** 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 * 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 * means to communicate actively with the computer (passively only drivers
* can interact with the computer by providing API functions). * 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() private val saveMonitor = new Object()
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
// State // IComputerContext
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
/** Starts asynchronous execution of this computer if it isn't running. */ def world = owner.world
def start(): Boolean = stateMonitor.synchronized(
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.Stopped && init() && {
state = State.Suspended state = State.Suspended
// Inject a dummy signal so that real one don't get swallowed. This way // 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") signal("dummy")
// Initialize any installed components.
for ((component, driver) <- components.values)
driver.instance.onInstall(this, component)
future = Executor.pool.submit(this) future = Executor.pool.submit(this)
true true
}) })
/** Stops a computer, possibly asynchronously. */ def stop() = saveMonitor.synchronized(stateMonitor.synchronized {
def stop(): Unit = saveMonitor.synchronized(stateMonitor.synchronized {
if (state != State.Stopped) { if (state != State.Stopped) {
if (state != State.Running) { if (state != State.Running) {
// If the computer is not currently running we can simply close it, // 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() { def update() {
stateMonitor.synchronized(state match { stateMonitor.synchronized(state match {
case State.Stopped | State.Stopping => return case State.Stopped | State.Stopping => return
@ -152,35 +221,48 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
// Update world time for computer threads. // Update world time for computer threads.
worldTime = owner.world.getWorldInfo().getWorldTotalTime() 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 // 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 lastUpdate = System.currentTimeMillis
} }
def signal(name: String, args: Any*) = { // ----------------------------------------------------------------------- //
args.foreach { // Note: driver interaction is synchronized, so we don't have to lock here.
case _: Byte | _: Short | _: Int | _: Long | _: Float | _: Double | _: String => Unit
case _ => throw new IllegalArgumentException() 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. def add(block: Block, x: Int, y: Int, z: Int, id: Int) =
case State.Stopped | State.Stopping => /* Nothing. */ Drivers.driverFor(block) match {
// Currently sleeping. Cancel that and start immediately. case None => None
case State.Sleeping => case Some(driver) if !components.contains(id) =>
future.cancel(true) val component = driver.instance.component(x, y, z)
state = State.Suspended components += id -> (component, driver)
signals.offer(new Signal(name, args.toArray)) driver.instance.onInstall(this, component)
future = Executor.pool.submit(this) signal("component_install", id)
// Running or in driver call or only a short yield, just push the signal. Some(driver.instance)
case _ =>
signals.offer(new Signal(name, args.toArray))
})
} }
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 = def readFromNBT(nbt: NBTTagCompound): Unit =
saveMonitor.synchronized(this.synchronized { saveMonitor.synchronized(this.synchronized {
// Clear out what we currently have, if anything. // Clear out what we currently have, if anything.
@ -347,7 +429,11 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
return false return false
} }
def init(): Boolean = { // ----------------------------------------------------------------------- //
// Internals
// ----------------------------------------------------------------------- //
private def init(): Boolean = {
// Creates a new state with all base libraries and the persistence library // 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 // 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. // rightfully should have, so we sandbox it a bit in the following.
@ -359,18 +445,38 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
try { try {
// Push a couple of functions that override original Lua API functions or // Push a couple of functions that override original Lua API functions or
// that add new functionality to it.1) // that add new functionality to it.
lua.getGlobal("os") 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() { lua.pushJavaFunction(new JavaFunction() {
def invoke(lua: LuaState): Int = { def invoke(lua: LuaState): Int = {
// Minecraft starts days at 6 o'clock, so we add those six hours. lua.pushBoolean(components.contains(lua.checkInteger(1)))
lua.pushNumber(worldTime + 6000)
return 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 // Custom os.clock() implementation returning the time the computer has
// been running, instead of the native library... // been running, instead of the native library...
@ -385,6 +491,29 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
}) })
lua.setField(-2, "clock") 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 // Custom os.difftime(). For most Lua implementations this would be the
// same anyway, but just to be on the safe side. // same anyway, but just to be on the safe side.
lua.pushJavaFunction(new JavaFunction() { 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 // building a table of permanent values used when persisting/unpersisting
// the state. // the state.
lua.newTable() lua.newTable()
lua.setGlobal("drivers") lua.setGlobal("driver")
Drivers.injectInto(this) Drivers.installOn(this)
// Run the boot script. This creates the global sandbox variable that is // Run the boot script. This creates the global sandbox variable that is
// used as the environment for any processes the kernel spawns, adds a // 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 return false
} }
def close(): Unit = stateMonitor.synchronized( private def close(): Unit = stateMonitor.synchronized(
if (state != State.Stopped) { if (state != State.Stopped) {
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.setTotalMemory(Integer.MAX_VALUE);
lua.close() lua.close()
lua = null lua = null
@ -595,15 +729,7 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
// Got a signal, inject it and call any handlers (if any). // Got a signal, inject it and call any handlers (if any).
case signal => { case signal => {
lua.pushString(signal.name) lua.pushString(signal.name)
signal.args.foreach { signal.args.foreach(lua.pushJavaObject)
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)
}
lua.resume(1, 1 + signal.args.length) lua.resume(1, 1 + signal.args.length)
} }
} }
@ -700,9 +826,10 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
/** The computer is paused and waiting for the game to resume. */ /** The computer is paused and waiting for the game to resume. */
val DriverReturnPaused = Value("DriverReturnPaused") val DriverReturnPaused = Value("DriverReturnPaused")
} }
}
/** Singleton for requesting executors that run our Lua states. */ /** Singleton for requesting executors that run our Lua states. */
private object Executor { private[computer] object Executor {
val pool = Executors.newScheduledThreadPool(Config.threads, val pool = Executors.newScheduledThreadPool(Config.threads,
new ThreadFactory() { new ThreadFactory() {
private val threadNumber = new AtomicInteger(1) private val threadNumber = new AtomicInteger(1)
@ -722,5 +849,4 @@ class Computer(val owner: IComputerEnvironment) extends IInternalComputerContext
return thread return thread
} }
}) })
}
} }

View File

@ -1,23 +1,43 @@
package li.cil.oc.server.computer package li.cil.oc.server.computer
import java.lang.reflect.Method import java.lang.reflect.Method
import scala.compat.Platform.EOL
import scala.reflect.runtime.universe._
import com.naef.jnlua.JavaFunction import com.naef.jnlua.JavaFunction
import com.naef.jnlua.LuaRuntimeException
import com.naef.jnlua.LuaState import com.naef.jnlua.LuaState
import li.cil.oc.OpenComputers
import li.cil.oc.api._
import li.cil.oc.api.Callback class ItemDriver(val instance: IItemDriver) extends Driver
import li.cil.oc.api.IDriver class BlockDriver(val instance: IBlockDriver) extends Driver
import li.cil.oc.common.computer.IInternalComputerContext
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. // Check if the component actually provides an API.
val api = driver.apiName val api = instance.apiName
if (api == null || api.isEmpty()) return 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. // Get or create table holding API tables.
lua.getGlobal("drivers") // ... drivers? lua.getGlobal("driver") // ... drivers?
assert(!lua.isNil(-1)) // ... drivers assert(!lua.isNil(-1)) // ... drivers
// Get or create API table. // Get or create API table.
@ -29,95 +49,125 @@ private[oc] class Driver(val driver: IDriver) {
lua.setField(-3, api) // ... drivers api lua.setField(-3, api) // ... drivers api
} // ... drivers api } // ... drivers api
for (method <- driver.getClass().getMethods()) val mirror = runtimeMirror(instance.getClass.getClassLoader)
method.getAnnotation(classOf[Callback]) match { val instanceMirror = mirror.reflect(instance)
case null => Unit // No annotation. val instanceType = instanceMirror.symbol.typeSignature
case annotation => { for (method <- instanceType.members collect { case m if m.isMethod => m.asMethod })
method.annotations collect {
case annotation: Callback => {
val name = annotation.name val name = annotation.name
lua.getField(-1, name) // ... drivers api func? lua.getField(-1, name) // ... drivers api func?
if (lua.isNil(-1)) { // ... drivers api nil if (lua.isNil(-1)) { // ... drivers api nil
// No such entry yet. // No such entry yet.
lua.pop(1) // ... drivers api 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 lua.setField(-2, name) // ... drivers api
} }
else { // ... drivers api func else { // ... drivers api func
// Entry already exists, skip it. // Entry already exists, skip it.
lua.pop(1) // ... drivers api lua.pop(1) // ... drivers api
// TODO Log warning properly via a logger. // Note that we can be sure it's an issue with two drivers
println("WARNING: Duplicate API entry, ignoring: " + api + "." + name) // 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 } // ... drivers api
lua.pop(2) // ... lua.pop(2) // ...
}
private class MethodWrapper(val context: IInternalComputerContext, val method: Method) extends JavaFunction { // Run custom init script.
def invoke(state: LuaState): Int = { val apiCode = instance.apiCode
return 0 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 { * This class is used to represent closures for driver API callbacks.
private val classOfBoolean = classOf[Boolean] *
private val classOfByte = classOf[Byte] * It stores the computer it is used by, to allow passing it along as the
private val classOfShort = classOf[Short] * {@see IComputerContext} for callbacks if they specify it as a parameter,
private val classOfInteger = classOf[Int] * and for interacting with the Lua state to pull parameters and push results
private val classOfLong = classOf[Long] * returned from the callback.
private val classOfFloat = classOf[Float] */
private val classOfDouble = classOf[Double] private class APIClosure(mirror: Mirror, val method: MethodMirror, val computer: Computer) extends JavaFunction {
private val classOfString = classOf[String] /**
private val parameterTypes = method.getParameterTypes.zipWithIndex * Based on the method's parameters we build a list of transformations
private val parameterCount = parameterTypes.size * that convert Lua input to the expected parameter type.
private val returnType = method.getReturnType */
private val returnsTuple = returnType.isInstanceOf[Array[Object]] val parameterTransformations = buildParameterTransformations(mirror, method.symbol)
private val returnsNothing = returnType.equals(Void.TYPE)
// TODO Rework all of this, most likely won't work because of type erasure. /**
def invoke(state: LuaState): Int = { * Based on the method's return value we build a callback that will push
// Parse the parameters, convert them to Java types. * that result to the stack, if any, and return the number of pushed values.
val parameters = Array(context) ++ parameterTypes.map { */
//case (classOfBoolean, i) => boolean2Boolean(state.checkBoolean(i + 1)) val returnTransformation = buildReturnTransformation(method.symbol)
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
}
// Call the actual function, grab the result, if any. /** This is the function actually called from the Lua state. */
val result = call(parameters: _*) def invoke(lua: LuaState): Int = {
return returnTransformation(computer,
// Check the result, convert it to Lua. method(parameterTransformations.map(_(computer)): _*))
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]) * This generates the transformation functions that are used to convert the
case classOfInteger => state.pushNumber(value.asInstanceOf[Int]) * Lua parameters to Java types to be passed on to the API function.
case classOfDouble => state.pushNumber(value.asInstanceOf[Double]) */
case classOfString => state.pushString(value.asInstanceOf[String]) private def buildParameterTransformations(mirror: Mirror, method: MethodSymbol): Array[Computer => Any] = {
case _ => state.pushNil() // No parameters?
if (method.paramss.length == 0 || method.paramss(0).length == 0) {
return Array()
} }
protected def call(args: AnyRef*) = { val params = method.paramss(0)
method.invoke(driver, args)
// 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
} }
*/
} }

View File

@ -1,37 +1,48 @@
package li.cil.oc.server.computer 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.IBlockDriver
import li.cil.oc.api.IItemDriver import li.cil.oc.api.IItemDriver
import li.cil.oc.common.computer.IInternalComputerContext
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
/** /**
* This class keeps track of registered drivers and provides installation logic * 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 * 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 * computers, since this class is used to determine whether an object is a
* valid component or not. * 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[oc] object Drivers {
private val blocks = Map.empty[Int, Driver] /** The list of registered block drivers. */
private val items = Map.empty[Int, Driver] 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. * Registers a new driver for a block component.
* *
* Whenever the neighboring blocks of a computer change, it checks if there * 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 * exists a driver for the changed block, and if so installs it.
* available components.
* *
* @param driver the driver for that block type. * @param driver the driver for that block type.
*/ */
def addDriver(driver: IBlockDriver) { def add(driver: IBlockDriver) {
if (blocks.contains(driver.blockType.blockID)) return if (locked) throw new IllegalStateException("Please register all drivers in the init phase.")
blocks += driver.blockType.blockID -> new Driver(driver) 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. * @param driver the driver for that item type.
*/ */
def addDriver(driver: IItemDriver) { def add(driver: IItemDriver) {
if (items.contains(driver.itemType.itemID)) return if (locked) throw new IllegalStateException("Please register all drivers in the init phase.")
items += driver.itemType.itemID -> new Driver(driver) 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))
} }

View File

@ -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*)
}

View File

@ -1,31 +1,29 @@
package li.cil.oc.server.drivers 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.ComponentType
import li.cil.oc.api.IItemDriver import li.cil.oc.api.IItemDriver
import li.cil.oc.server.components.Disk import li.cil.oc.server.components.Disk
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import li.cil.oc.api.Callback
object HDDDriver extends IItemDriver { object HDDDriver extends IItemDriver {
def componentName = "disk" def componentName = "disk"
def apiName = "disk" override def apiName = "disk"
def apiCode = null
@Callback(name = "mount") @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() component.asInstanceOf[HDDComponent].close()
} }
} }