mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-08 14:50:51 -04:00
working on drivers
This commit is contained in:
parent
baad2ebbda
commit
f9cd7fd3b6
@ -58,7 +58,7 @@ do
|
||||
end
|
||||
end
|
||||
end
|
||||
wrapRecursive(drivers)
|
||||
wrapRecursive(driver)
|
||||
end
|
||||
|
||||
--[[ Permanent value tables.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -5,6 +5,7 @@ object Config {
|
||||
var blockMonitorId = 3651
|
||||
|
||||
var itemHDDId = 4600
|
||||
var itemGPUId = 4601
|
||||
|
||||
var threads = 4
|
||||
}
|
@ -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")
|
||||
|
@ -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
|
||||
* <tt>Map</tt>,<tt>List</tt> 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:
|
||||
*
|
||||
* <pre>
|
||||
* 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)
|
||||
@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();
|
||||
}
|
@ -12,5 +12,4 @@ object ComponentType extends Enumeration {
|
||||
val RAM = Value("RAM")
|
||||
val HDD = Value("HDD")
|
||||
val PCI = Value("PCI")
|
||||
val GPU = Value("GPU")
|
||||
}
|
@ -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
|
||||
}
|
@ -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 <code>drivers.component</code>. 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 <code>drivers.disk</code>.
|
||||
*
|
||||
* 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 <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.
|
||||
*/
|
||||
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 <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.
|
||||
*/
|
||||
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) = {}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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) {}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
@ -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*)
|
||||
}
|
@ -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()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user