rudimentary keyboard input is working

This commit is contained in:
Florian Nücke 2013-09-23 18:13:24 +02:00
parent 48cf012d67
commit 29a79b5ed0
16 changed files with 253 additions and 91 deletions

View File

@ -2,25 +2,25 @@
driver.gpu = {} driver.gpu = {}
function driver.gpu.setResolution(gpu, screen, w, h) function driver.gpu.setResolution(gpu, screen, w, h)
sendToNode(gpu, "gpu.resolution=", screen, w, h) sendToNode(gpu, "gpu.resolution=", screen, w, h)
end end
function driver.gpu.getResolution(gpu, screen) function driver.gpu.getResolution(gpu, screen)
return sendToNode(gpu, "gpu.resolution", screen) return sendToNode(gpu, "gpu.resolution", screen)
end end
function driver.gpu.getResolutions(gpu, screen) function driver.gpu.getResolutions(gpu, screen)
return sendToNode(gpu, "gpu.resolutions", screen) return sendToNode(gpu, "gpu.resolutions", screen)
end end
function driver.gpu.set(gpu, screen, col, row, value) function driver.gpu.set(gpu, screen, col, row, value)
sendToNode(gpu, "gpu.set", screen, col, row, value) sendToNode(gpu, "gpu.set", screen, col, row, value)
end end
function driver.gpu.fill(gpu, screen, col, row, w, h, value) function driver.gpu.fill(gpu, screen, col, row, w, h, value)
sendToNode(gpu, "gpu.fill", screen, col, row, w, h, value:sub(1, 1)) sendToNode(gpu, "gpu.fill", screen, col, row, w, h, value:sub(1, 1))
end end
function driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty) function driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty)
sendToNode(gpu, "gpu.copy", screen, col, row, w, h, tx, ty) sendToNode(gpu, "gpu.copy", screen, col, row, w, h, tx, ty)
end end

View File

@ -86,12 +86,15 @@ end
-- Main OS loop, keeps everything else running. -- Main OS loop, keeps everything else running.
while true do while true do
local signal, id = os.signal(nil, 2) local signal, param = os.signal(nil, 2)
if signal == "component_added" then if signal == "component_added" then
onInstall(id) onInstall(param)
elseif signal == "component_removed" then elseif signal == "component_removed" then
onUninstall(id) onUninstall(param)
elseif signal == "key_down" then
write(param)
else
write("Clock: ")
print(os.clock())
end end
write("Clock: ")
print(os.clock())
end end

View File

@ -0,0 +1,137 @@
--[[ API for keyboards. ]]
driver.keyboard = {}
driver.keyboard.keys = {
["1"] = 0x02,
["2"] = 0x03,
["3"] = 0x04,
["4"] = 0x05,
["5"] = 0x06,
["6"] = 0x07,
["7"] = 0x08,
["8"] = 0x09,
["9"] = 0x0A,
["0"] = 0x0B,
a = 0x1E,
b = 0x30,
c = 0x2E,
d = 0x20,
e = 0x12,
f = 0x21,
g = 0x22,
h = 0x23,
i = 0x17,
j = 0x24,
k = 0x25,
l = 0x26,
m = 0x32,
n = 0x31,
o = 0x18,
p = 0x19,
q = 0x10,
r = 0x13,
s = 0x1F,
t = 0x14,
u = 0x16,
v = 0x2F,
w = 0x11,
x = 0x2D,
y = 0x15,
z = 0x2C,
apostrophe = 0x28,
at = 0x91,
back = 0x0E, -- backspace
backslash = 0x2B,
colon = 0x92,
comma = 0x33,
enter = 0x1C,
equals = 0x0D,
grave = 0x29, -- accent grave
lbracket = 0x1A,
lcontrol = 0x1D,
lmenu = 0x38, -- left Alt
lshift = 0x2A,
minus = 0x0C,
numlock = 0x45,
pause = 0xC5,
period = 0x34,
rbracket = 0x1B,
rcontrol = 0x9D,
rmenu = 0xB8, -- right Alt
rshift = 0x36,
scroll = 0x46, -- Scroll Lock
semicolon = 0x27,
slash = 0x35, -- / on main keyboard
space = 0x39,
stop = 0x95,
tab = 0x0F,
underline = 0x93,
-- Keypad (and numpad with numlock off)
up = 0xC8,
down = 0xD0,
left = 0xCB,
right = 0xCD,
home = 0xC7,
["end"] = 0xCF,
pageUp = 0xC9,
pageDown = 0xD1,
insert = 0xD2,
delete = 0xD3,
-- Function keys
f1 = 0x3B,
f2 = 0x3C,
f3 = 0x3D,
f4 = 0x3E,
f5 = 0x3F,
f6 = 0x40,
f7 = 0x41,
f8 = 0x42,
f9 = 0x43,
f10 = 0x44,
f11 = 0x57,
f12 = 0x58,
f13 = 0x64,
f14 = 0x65,
f15 = 0x66,
f16 = 0x67,
f17 = 0x68,
f18 = 0x69,
f19 = 0x71,
-- Japanese keyboards
kana = 0x70,
kanji = 0x94,
convert = 0x79,
noconvert = 0x7B,
yen = 0x7D,
circumflex = 0x90,
ax = 0x96,
-- Numpad
numpad0 = 0x52,
numpad1 = 0x4F,
numpad2 = 0x50,
numpad3 = 0x51,
numpad4 = 0x4B,
numpad5 = 0x4C,
numpad6 = 0x4D,
numpad7 = 0x47,
numpad8 = 0x48,
numpad9 = 0x49,
numpadmul = 0x37,
numpaddiv = 0xB5,
numpadsub = 0x4A,
numpadadd = 0x4E,
numpaddecimal = 0x53,
numpadcomma = 0xB3,
numpadenter = 0x9C,
numpadequals = 0x8D,
}
-- Create inverse mapping for name lookup.
for k, v in pairs(driver.keyboard.keys) do
driver.keyboard.keys[v] = k
end

View File

@ -1,15 +1,13 @@
package li.cil.oc package li.cil.oc
import li.cil.oc.common.block.BlockComputer import li.cil.oc.common.block._
import li.cil.oc.common.block.BlockMulti
import li.cil.oc.common.block.BlockScreen
import li.cil.oc.common.block.BlockSpecialMulti
object Blocks { object Blocks {
var blockSimple: BlockMulti = null var blockSimple: BlockMulti = null
var blockSpecial: BlockMulti = null var blockSpecial: BlockMulti = null
var computer: BlockComputer = null var computer: BlockComputer = null
var screen: BlockScreen = null var screen: BlockScreen = null
var keyboard: BlockKeyboard = null
def init() { def init() {
// IMPORTANT: the multi block must come first, since the sub blocks will // IMPORTANT: the multi block must come first, since the sub blocks will
@ -20,5 +18,6 @@ object Blocks {
computer = new BlockComputer(blockSimple) computer = new BlockComputer(blockSimple)
screen = new BlockScreen(blockSimple) screen = new BlockScreen(blockSimple)
keyboard = new BlockKeyboard(blockSpecial)
} }
} }

View File

@ -1,6 +1,5 @@
package li.cil.oc.api package li.cil.oc.api
import _root_.scala.beans.BeanProperty
import net.minecraft.world.World import net.minecraft.world.World
/** /**
@ -10,6 +9,9 @@ import net.minecraft.world.World
* placed in the world, but cannot be modified to or don't want to have their * placed in the world, but cannot be modified to or don't want to have their
* `TileEntities` implement `INetworkNode`. * `TileEntities` implement `INetworkNode`.
* <p/> * <p/>
* A block driver is used by proxy blocks to check its neighbors and whether
* those neighbors should be treated as components or not.
* <p/>
* Note that it is possible to write one driver that supports as many different * 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 * blocks as you wish. I'd recommend writing one per device (type), though, to
* keep things modular. * keep things modular.
@ -34,15 +36,17 @@ trait IBlockDriver extends IDriver {
/** /**
* Get a reference to the network node wrapping the specified block. * Get a reference to the network node wrapping the specified block.
* <p/> * <p/>
* This is used to provide context to the driver's methods, for example when * This is used to connect the component to the component network when it is
* an API method is called this will always be passed as the first parameter. * detected next to a proxy. Components that are not part of the component
* network probably don't make much sense (can't think of any uses at this
* time), but you may still opt to not implement this.
* *
* @param world the world in which the block to get the component for lives. * @param world the world in which the block to get the node for lives.
* @param x the X coordinate of the block to get the component for. * @param x the X coordinate of the block to get the node for.
* @param y the Y coordinate of the block to get the component for. * @param y the Y coordinate of the block to get the node for.
* @param z the Z coordinate of the block to get the component for. * @param z the Z coordinate of the block to get the node for.
* @return the block component at that location, controlled by this driver. * @return the network node for the block at that location.
*/ */
@BeanProperty def node(world: World, x: Int, y: Int, z: Int): INetworkNode =
def node(world: World, x: Int, y: Int, z: Int): INetworkNode world.getBlockTileEntity(x, y, z).asInstanceOf[INetworkNode]
} }

View File

@ -10,36 +10,36 @@ import java.io.InputStream
* 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.
* <p/> * <p/>
* Note that drivers themselves are singletons. They can define a parameter of * Do not implement this interface directly; use the `IItemDriver` and
* type {@link IComputerContext} in their API functions which will hold the * `IBlockDriver` interfaces for the respective component types.
* 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.
* <p/>
* Do not implement this interface directly; use the {@link IItemDriver} and
* {@link IBlockDriver} interfaces for the respective component types.
*/ */
trait IDriver { trait IDriver {
/** /**
* Some initialization code that is run when the driver is installed. * Some initialization code that is run when the driver is installed.
* <p/> * <p/>
* This is loaded * These will usually be some functions that generate network messages of
* into the Lua state and run in the global, un-sandboxed environment. This * the particular signature the node of the driver handles, but may contain
* means your scripts can mess things up bad, so make sure you know what * arbitrary other functions. However, whatever you do, keep in mind that
* you're doing and exposing. * only certain parts of the global namespace will be made available to the
* computer at runtime, so it's best to keep all you declare in the driver
* table (global variable `driver`).
* <p/> * <p/>
* This can be null to do nothing. Otherwise this is expected to be valid Lua * This is loaded into the Lua state and run in the global, un-sandboxed
* code (it is simply loaded via <code>load()</code> and then executed). * environment. This means your scripts can mess things up bad, so make sure
* you know what you're doing and exposing.
* <p/>
* This can be `None` to do nothing. Otherwise this is expected to be valid
* Lua code (it is simply loaded via <code>load()</code> and then executed).
* <p/> * <p/>
* The stream has to be recreated each time this is called. Normally you will * The stream has to be recreated each time this is called. Normally you will
* return something along the lines of * return something along the lines of
* <code>Mod.class.getResourceAsStream("/assets/mod/lua/ocapi.lua")</code> * `Mod.class.getResourceAsStream("/assets/yourmod/lua/ocapi.lua")`
* from this method. If you wish to hard-code the returned script, you can use * from this method. If you wish to hard-code the returned script, you can use
* <code>new ByteArrayInputStream(yourScript.getBytes())</code> instead. Note * `new ByteArrayInputStream(yourScript.getBytes())` instead.
* that the stream will automatically closed. * <p/>
* IMPORTANT: Note that the stream will automatically closed.
* *
* @return the Lua code to run after installing the API table. * @return the Lua code to run when a computer is started up.
*/ */
def api: Option[InputStream] = None def api: Option[InputStream] = None
} }

View File

@ -13,7 +13,7 @@ import net.minecraft.item.ItemStack
* queried using the drivers' `worksWith` functions. The first driver that * queried using the drivers' `worksWith` functions. The first driver that
* replies positively and whose check against the slot type is successful, i.e. * replies positively and whose check against the slot type is successful, i.e.
* for which the `componentType` matches the slot, will be used as the * for which the `componentType` matches the slot, will be used as the
* component's driver and the component will be installed. If no driver is found * component's driver and the component will be added. If no driver is found
* the item will be rejected and cannot be installed. * the item will be rejected and cannot be installed.
* <p/> * <p/>
* Note that it is possible to write one driver that supports as many different * Note that it is possible to write one driver that supports as many different
@ -47,6 +47,11 @@ trait IItemDriver extends IDriver {
/** /**
* Gets a reference to the network node interfacing the specified item. * Gets a reference to the network node interfacing the specified item.
* <p/>
* This is used to connect the component to the component network when it is
* added to a computer, for example. Components that are not part of the
* component network probably don't make much sense (can't think of any uses
* at this time), but you may still opt to not implement this.
* *
* @param item the item instance for which to get the node. * @param item the item instance for which to get the node.
* @return the network node for that item. * @return the network node for that item.

View File

@ -27,20 +27,22 @@ object PacketSender {
pb.sendToServer() pb.sendToServer()
} }
def sendKeyDown[T <: TileEntity with INetworkNode](t: T, c: Char) = { def sendKeyDown[T <: TileEntity with INetworkNode](t: T, char: Char, code: Int) = {
val pb = new PacketBuilder(PacketType.KeyDown) val pb = new PacketBuilder(PacketType.KeyDown)
pb.writeTileEntity(t) pb.writeTileEntity(t)
pb.writeChar(c) pb.writeChar(char)
pb.writeInt(code)
pb.sendToServer() pb.sendToServer()
} }
def sendKeyUp[T <: TileEntity with INetworkNode](t: T, c: Char) = { def sendKeyUp[T <: TileEntity with INetworkNode](t: T, char: Char, code: Int) = {
val pb = new PacketBuilder(PacketType.KeyUp) val pb = new PacketBuilder(PacketType.KeyUp)
pb.writeTileEntity(t) pb.writeTileEntity(t)
pb.writeChar(c) pb.writeChar(char)
pb.writeInt(code)
pb.sendToServer() pb.sendToServer()
} }

View File

@ -1,13 +1,11 @@
package li.cil.oc.client.gui package li.cil.oc.client.gui
import li.cil.oc.client.PacketSender import li.cil.oc.client.PacketSender
import li.cil.oc.common.tileentity.TileEntityKeyboard
import li.cil.oc.common.tileentity.TileEntityScreen import li.cil.oc.common.tileentity.TileEntityScreen
import net.minecraft.client.renderer.GLAllocation import net.minecraft.client.renderer.GLAllocation
import net.minecraft.client.renderer.Tessellator import net.minecraft.client.renderer.Tessellator
import net.minecraft.client.renderer.texture.TextureManager import net.minecraft.client.renderer.texture.TextureManager
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.common.ForgeDirection
import org.lwjgl.input.Keyboard import org.lwjgl.input.Keyboard
import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL11
@ -49,16 +47,15 @@ class GuiScreen(val tileEntity: TileEntityScreen) extends net.minecraft.client.g
/** Must be called whenever the buffer of the underlying screen changes. */ /** Must be called whenever the buffer of the underlying screen changes. */
def updateText() = GuiScreen.compileText(scale, tileEntity.screen.lines) def updateText() = GuiScreen.compileText(scale, tileEntity.screen.lines)
override def handleKeyboardInput() = { override def keyTyped(char: Char, code: Int) = {
// Find all keyboards next to this screen and type on them. super.keyTyped(char, code)
for (k <- neighboringKeyboards) { if (code != Keyboard.KEY_ESCAPE && code != Keyboard.KEY_F11)
if (Keyboard.getEventKeyState) { if (Keyboard.getEventKeyState) {
PacketSender.sendKeyDown(k, Keyboard.getEventCharacter) PacketSender.sendKeyDown(tileEntity, char, code)
} }
else { else {
PacketSender.sendKeyUp(k, Keyboard.getEventCharacter) PacketSender.sendKeyUp(tileEntity, char, code)
} }
}
} }
override def initGui() = { override def initGui() = {
@ -85,16 +82,6 @@ class GuiScreen(val tileEntity: TileEntityScreen) extends net.minecraft.client.g
} }
override def doesGuiPauseGame = false override def doesGuiPauseGame = false
private def neighboringKeyboards =
ForgeDirection.VALID_DIRECTIONS.
map(d => tileEntity.worldObj.getBlockTileEntity(
tileEntity.xCoord + d.offsetX,
tileEntity.yCoord + d.offsetY,
tileEntity.zCoord + d.offsetZ)).
filter(_ != null).
filter(_.isInstanceOf[TileEntityKeyboard]).
map(_.asInstanceOf[TileEntityKeyboard])
} }
/** We cache OpenGL stuff in a singleton to avoid having to re-allocate. */ /** We cache OpenGL stuff in a singleton to avoid having to re-allocate. */

View File

@ -30,6 +30,7 @@ class Proxy {
NetworkRegistry.instance.registerGuiHandler(OpenComputers, GuiHandler) NetworkRegistry.instance.registerGuiHandler(OpenComputers, GuiHandler)
OpenComputersAPI.addDriver(GraphicsCardDriver) OpenComputersAPI.addDriver(GraphicsCardDriver)
OpenComputersAPI.addDriver(KeyboardDriver)
MinecraftForge.EVENT_BUS.register(ForgeEventHandler) MinecraftForge.EVENT_BUS.register(ForgeEventHandler)
} }

View File

@ -19,14 +19,16 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with ICo
private val hasChanged = new AtomicBoolean(true) private val hasChanged = new AtomicBoolean(true)
private var isRunning = computer.isRunning private var isRunning = false
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
// NetworkNode // NetworkNode
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def receive(message: INetworkMessage) = message.data match { override def receive(message: INetworkMessage) = message.data match {
case Array() if message.name == "network.connect" => // The isRunning check is here to avoid network.connect messages being sent
// while loading a chunk (thus leading to "false" component_added signals).
case Array() if message.name == "network.connect" && isRunning =>
computer.signal("component_added", message.source.address); None computer.signal("component_added", message.source.address); None
case Array() if message.name == "network.disconnect" => case Array() if message.name == "network.disconnect" =>
computer.signal("component_removed", message.source.address); None computer.signal("component_removed", message.source.address); None

View File

@ -7,16 +7,16 @@ class TileEntityKeyboard extends TileEntityRotatable with INetworkNode {
override def name = "keyboard" override def name = "keyboard"
override def receive(message: INetworkMessage) = message.data match { override def receive(message: INetworkMessage) = message.data match {
case Array(name: String, p: Player, c: Character) if name == "keyboard.keyDown" => { case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" => {
// TODO check if player is close enough and only consume message if so // TODO check if player is close enough and only consume message if so
network.sendToAll(this, "signal", "key_down", c) network.sendToAll(this, "signal", "key_down", char.toString, code)
message.cancel() message.cancel() // One keyboard is enough.
None None
} }
case Array(name: String, p: Player, c: Character) if name == "keyboard.keyUp" => { case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyUp" => {
// TODO check if player is close enough and only consume message if so // TODO check if player is close enough and only consume message if so
network.sendToAll(this, "signal", "key_up", c) network.sendToAll(this, "signal", "key_up", char.toString, code)
message.cancel() message.cancel() // One keyboard is enough.
None None
} }
case _ => None case _ => None

View File

@ -67,12 +67,12 @@ class PacketHandler extends CommonPacketHandler {
def onKeyDown(p: PacketParser) = def onKeyDown(p: PacketParser) =
p.readTileEntity[INetworkNode]() match { p.readTileEntity[INetworkNode]() match {
case None => // Invalid packet. case None => // Invalid packet.
case Some(n) => n.network.sendToAll(n, "keyboard.keyDown", p.player, p.readChar()) case Some(n) => n.network.sendToAll(n, "keyboard.keyDown", p.player, p.readChar(), p.readInt())
} }
def onKeyUp(p: PacketParser) = def onKeyUp(p: PacketParser) =
p.readTileEntity[INetworkNode]() match { p.readTileEntity[INetworkNode]() match {
case None => // Invalid packet. case None => // Invalid packet.
case Some(n) => n.network.sendToAll(n, "keyboard.keyUp", p.player, p.readChar()) case Some(n) => n.network.sendToAll(n, "keyboard.keyUp", p.player, p.readChar(), p.readInt())
} }
} }

View File

@ -660,12 +660,13 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// resuming the state again. // resuming the state again.
val sleep = (lua.toNumber(2) * 1000).toLong val sleep = (lua.toNumber(2) * 1000).toLong
lua.pop(results) lua.pop(results)
state = State.Sleeping if (signals.isEmpty) {
future = Some(Executor.pool. state = State.Sleeping
schedule(this, sleep, TimeUnit.MILLISECONDS)) future = Some(Executor.pool.schedule(this, sleep, TimeUnit.MILLISECONDS))
} else future = Some(Executor.pool.submit(this))
} }
else if (results == 1 && lua.isFunction(2)) { else if (results == 1 && lua.isFunction(2)) {
// If we get one function it's a wrapper for a driver call. // If we get one function it's a wrapper for a synchronized call.
state = State.SynchronizedCall state = State.SynchronizedCall
future = None future = None
} }
@ -673,7 +674,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// Something else, just pop the results and try again. // Something else, just pop the results and try again.
lua.pop(results) lua.pop(results)
state = State.Suspended state = State.Suspended
future = Some(Executor.pool.submit(this)) if (signals.isEmpty) future = None
else future = Some(Executor.pool.submit(this))
} }
} }

View File

@ -1,5 +1,6 @@
package li.cil.oc.server.computer package li.cil.oc.server.computer
import li.cil.oc.OpenComputers
import li.cil.oc.api.INetwork import li.cil.oc.api.INetwork
import li.cil.oc.api.INetworkMessage import li.cil.oc.api.INetworkMessage
import li.cil.oc.api.INetworkNode import li.cil.oc.api.INetworkNode
@ -35,10 +36,9 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No
nodes.values.flatten.foreach(_.data.network = this) nodes.values.flatten.foreach(_.data.network = this)
def connect(nodeA: INetworkNode, nodeB: INetworkNode) = try { def connect(nodeA: INetworkNode, nodeB: INetworkNode) = {
if (locked) throw new IllegalStateException( if (locked) throw new IllegalStateException(
"Cannot modify network while it is already updating its structure.") "Cannot modify network while it is already updating its structure.")
locked = true
val containsA = nodes.get(nodeA.address).exists(_.exists(_.data == nodeA)) val containsA = nodes.get(nodeA.address).exists(_.exists(_.data == nodeA))
val containsB = nodes.get(nodeB.address).exists(_.exists(_.data == nodeB)) val containsB = nodes.get(nodeB.address).exists(_.exists(_.data == nodeB))
@ -66,9 +66,6 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No
else if (containsA) add(nodes(nodeA.address).find(_.data == nodeA).get, nodeB) else if (containsA) add(nodes(nodeA.address).find(_.data == nodeA).get, nodeB)
else add(nodes(nodeB.address).find(_.data == nodeB).get, nodeA) else add(nodes(nodeB.address).find(_.data == nodeB).get, nodeA)
} }
finally {
locked = false
}
def remove(node: INetworkNode) = nodes.get(node.address) match { def remove(node: INetworkNode) = nodes.get(node.address) match {
case None => false case None => false
@ -103,9 +100,14 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No
private def send(message: Network.Message, nodes: Iterator[INetworkNode]) = { private def send(message: Network.Message, nodes: Iterator[INetworkNode]) = {
var result = None: Option[Array[Any]] var result = None: Option[Array[Any]]
while (!message.isCanceled && nodes.hasNext) { while (!message.isCanceled && nodes.hasNext) {
nodes.next().receive(message) match { try {
case None => // Ignore. nodes.next().receive(message) match {
case r => result = r case None => // Ignore.
case r => result = r
}
} catch {
case e: Throwable =>
OpenComputers.log.warning("Error in message handler:\n" + e.getStackTraceString)
} }
} }
result result
@ -124,7 +126,6 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No
newNode newNode
} }
else { else {
val otherNetwork = node.network.asInstanceOf[Network]
// We have to merge. First create a copy of the old nodes to have the // We have to merge. First create a copy of the old nodes to have the
// list of nodes to which to send "network.connect" messages. // list of nodes to which to send "network.connect" messages.
val oldNodes = nodes.values.flatten.map(_.data).toArray val oldNodes = nodes.values.flatten.map(_.data).toArray
@ -132,7 +133,12 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No
// iteration to merge into this network, used to send "network.reconnect" // iteration to merge into this network, used to send "network.reconnect"
// messages to old nodes in case we have to change a node's address to // messages to old nodes in case we have to change a node's address to
// ensure unique addresses in the merged network. // ensure unique addresses in the merged network.
val otherNetwork = node.network.asInstanceOf[Network]
val otherNodes = otherNetwork.nodes.values.flatten.map(_.data) val otherNodes = otherNetwork.nodes.values.flatten.map(_.data)
// Lock this network to avoid message handlers adding nodes, which could
// lead to addresses getting taken that we will need for the nodes we are
// about to merge into this network.
locked = true
// Pre-merge step: ensure addresses are unique. // Pre-merge step: ensure addresses are unique.
for (node <- otherNodes if nodes.contains(node.address)) { for (node <- otherNodes if nodes.contains(node.address)) {
val oldAddress = node.address val oldAddress = node.address
@ -148,6 +154,8 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No
node.data.network = this node.data.network = this
send(new Network.Message(node.data, "network.connect"), oldNodes.iterator) send(new Network.Message(node.data, "network.connect"), oldNodes.iterator)
} }
// Done, unlock again.
locked = false
// Return the node object of the newly connected node for the next step. // Return the node object of the newly connected node for the next step.
nodes(node.address).find(_.data == node).get nodes(node.address).find(_.data == node).get
} }

View File

@ -0,0 +1,12 @@
package li.cil.oc.server.drivers
import li.cil.oc.Blocks
import li.cil.oc.api.IBlockDriver
import net.minecraft.world.World
object KeyboardDriver extends IBlockDriver {
override def api = Option(getClass.getResourceAsStream("/assets/opencomputers/lua/keyboard.lua"))
override def worksWith(world: World, x: Int, y: Int, z: Int) =
world.getBlockId(x, y, z) == Blocks.keyboard.blockId
}