diff --git a/assets/opencomputers/lua/kernel.lua b/assets/opencomputers/lua/kernel.lua index de36b1fa6..bb3ecd535 100644 --- a/assets/opencomputers/lua/kernel.lua +++ b/assets/opencomputers/lua/kernel.lua @@ -264,7 +264,7 @@ sandbox = { (type(timeout) == "number" and timeout or math.huge) repeat local signal = table.pack(coroutine.yield(deadline - os.uptime())) - if signal.n > 0 then -- not a "blind" resume? + if signal.n > 0 then return table.unpack(signal, 1, signal.n) end until os.uptime() >= deadline @@ -295,12 +295,13 @@ sandbox = { return nil, reason end local proxy = {address = address, type = type} - local methods = component.methods(address) - if methods then - for method, direct in pairs(methods) do - proxy[method] = function(...) - return invoke(direct, address, method, ...) - end + local methods, reason = component.methods(address) + if not methods then + return nil, reason + end + for method, direct in pairs(methods) do + proxy[method] = function(...) + return invoke(direct, address, method, ...) end end return proxy diff --git a/assets/opencomputers/textures/gui/robot.png b/assets/opencomputers/textures/gui/robot.png new file mode 100644 index 000000000..2bcd4055f Binary files /dev/null and b/assets/opencomputers/textures/gui/robot.png differ diff --git a/li/cil/oc/api/driver/Slot.java b/li/cil/oc/api/driver/Slot.java index 8bd14c499..205fafc57 100644 --- a/li/cil/oc/api/driver/Slot.java +++ b/li/cil/oc/api/driver/Slot.java @@ -4,32 +4,36 @@ package li.cil.oc.api.driver; * List of possible item component types. *
* This is used to determine which item components may go into which slots in - * a computer, since unlike block components, item components must be placed - * inside the computer, not next to it. + * a computer's inventory. */ public enum Slot { + /** + * Extension cards such as graphics cards or redstone cards. + */ + Card, + + /** + * Floppy disks. These can be inserted into the Disk Drive block. + */ + Disk, + + /** + * Hard disk drives. These can be installed in computers. + */ + HardDiskDrive, + + /** + * Memory extension components. Used to increase computer RAM. + */ + Memory, + /** * Power generating components, such as generators. */ Power, /** - * Memory extension components. + * Tools that can be used by robots. */ - Memory, - - /** - * Hard disk drives. - */ - HardDiskDrive, - - /** - * Extension cards such as graphics or redstone cards. - */ - Card, - - /** - * Floppy disks. - */ - Disk + Tool } \ No newline at end of file diff --git a/li/cil/oc/api/network/LuaCallback.java b/li/cil/oc/api/network/LuaCallback.java index 7ee24b26e..768a51921 100644 --- a/li/cil/oc/api/network/LuaCallback.java +++ b/li/cil/oc/api/network/LuaCallback.java @@ -38,10 +38,9 @@ public @interface LuaCallback { * This is mainly intended to allow functions to perform faster than when * called 'synchronously' (where the call takes at least one server tick). * - * Note that {@link Network} interaction is mostly synchronized - i.e. the - * operations on the network itself are: once you get some result you're - * not guaranteed that node you just fetched is still in the - * network, for example! + * Keep in mind that the node {@link Network} is not thread safe! + * Be sure you know what you're doing if you're working with a node's + * network in a direct callback. */ boolean direct() default false; @@ -63,6 +62,9 @@ public @interface LuaCallback { * via a direct call to {@link Component#invoke(String, Context, Object...)} * from the host side. Also, this limit is per-computer, so the method may * be invoked more often than this per tick, if different computers call it. + * + * An exception to that rule is {@link Connector#changeBuffer(double)}, + * which is synchronized, so you can consume/produce power in direct calls. */ int limit() default Integer.MAX_VALUE; } diff --git a/li/cil/oc/api/network/Network.java b/li/cil/oc/api/network/Network.java index 9ad8b9ada..3473fb3d8 100644 --- a/li/cil/oc/api/network/Network.java +++ b/li/cil/oc/api/network/Network.java @@ -33,6 +33,10 @@ package li.cil.oc.api.network; * nodes of the other network(s), when a network is split (all pairs). * * + * Note that network access is not thread safe! Networks should only + * be accessed from the main server thread. + * + * * IMPORTANT: do *not* implement this interface yourself and create * instances of your own network implementation; this will lead to * incompatibilities with the built-in network implementation (which can only diff --git a/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala b/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala index cfda8122c..b6a49bd1c 100644 --- a/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala +++ b/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala @@ -24,9 +24,10 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with /** We cache the display lists for the screens we render for performance. */ val cache = com.google.common.cache.CacheBuilder.newBuilder(). - expireAfterAccess(5, TimeUnit.SECONDS). + expireAfterAccess(2, TimeUnit.SECONDS). removalListener(this). - asInstanceOf[CacheBuilder[Screen, Int]].build[Screen, Int]() + asInstanceOf[CacheBuilder[Screen, Int]]. + build[Screen, Int]() /** Used to pass the current screen along to call(). */ private var screen: Screen = null @@ -68,19 +69,18 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with MonospaceFontRenderer.init(tileEntityRenderer.renderEngine) val list = cache.get(screen, this) - compile(list) - GL11.glCallList(list) + compileOrDraw(list) GL11.glPopMatrix() GL11.glPopAttrib() } - private def compile(list: Int) = if (screen.hasChanged) { + private def compileOrDraw(list: Int) = if (screen.hasChanged && !RenderState.compilingDisplayList) { screen.hasChanged = false val (sx, sy) = (screen.width, screen.height) val (tw, th) = (sx * 16f, sy * 16f) - GL11.glNewList(list, GL11.GL_COMPILE) + GL11.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE) screen.yaw match { case ForgeDirection.WEST => GL11.glRotatef(-90, 0, 1, 0) @@ -137,7 +137,10 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with } GL11.glEndList() + + true } + else GL11.glCallList(list) private def playerDistanceSq() = { val player = Minecraft.getMinecraft.thePlayer @@ -191,12 +194,12 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with def call = { val list = GLAllocation.generateDisplayLists(1) screen.hasChanged = true // Force compilation. - compile(list) list } - def onRemoval(e: RemovalNotification[TileEntity, Int]) = + def onRemoval(e: RemovalNotification[TileEntity, Int]) { GLAllocation.deleteDisplayLists(e.getValue) + } // ----------------------------------------------------------------------- // // ITickHandler diff --git a/li/cil/oc/common/GuiType.scala b/li/cil/oc/common/GuiType.scala index e557d5558..b9f72df59 100644 --- a/li/cil/oc/common/GuiType.scala +++ b/li/cil/oc/common/GuiType.scala @@ -1,7 +1,5 @@ package li.cil.oc.common object GuiType extends Enumeration { - val Case = Value("Case") - val DiskDrive = Value("DiskDrive") - val Screen = Value("Screen") + val Case, DiskDrive, Screen = Value } diff --git a/li/cil/oc/common/PacketType.scala b/li/cil/oc/common/PacketType.scala index 0cff1ae61..1730a7cb9 100644 --- a/li/cil/oc/common/PacketType.scala +++ b/li/cil/oc/common/PacketType.scala @@ -1,39 +1,21 @@ package li.cil.oc.common object PacketType extends Enumeration { - /** - * Computer running / stopped. - * - * Same as for screen, but for computer running state. The running state is - * used on the client side to display different textures based on whether the - * computer is running or not. - */ val ComputerStateRequest = Value("ComputerStateRequest") val ComputerStateResponse = Value("ComputerStateResponse") - /** Sent by power distributors for client display of average buffer fill. */ val PowerStateRequest = Value("PowerStateRequest") val PowerStateResponse = Value("PowerStateResponse") - /** Sent by redstone capable blocks (e.g. computers). */ val RedstoneStateRequest = Value("RedstoneStateRequest") val RedstoneStateResponse = Value("RedstoneStateResponse") - /** Sent by rotatable tile entities to notify clients of changes. */ val RotatableStateRequest = Value("RotatableStateRequest") val RotatableStateResponse = Value("RotatableStateResponse") - /** - * Full buffer request / response. - * - * This is necessary when a tile entity containing a screen is created for - * the first time on the client side, for example, since tile entities are - * not automatically synchronized via read/write NBT. - */ val ScreenBufferRequest = Value("ScreenBufferRequest") val ScreenBufferResponse = Value("ScreenBufferResponse") - /** These are sent from the server to the client for partial updates. */ val ScreenColorChange = Value("ScreenColorChange") val ScreenCopy = Value("ScreenCopy") val ScreenDepthChange = Value("ScreenDepthChange") @@ -42,11 +24,9 @@ object PacketType extends Enumeration { val ScreenResolutionChange = Value("ScreenResolutionChange") val ScreenSet = Value("ScreenSet") - /** Sent by analyzer tool when used. */ - val Analyze = Value("Analyze") - - /** Sent by clients on keyboard input for computers. */ val KeyDown = Value("KeyDown") val KeyUp = Value("KeyUp") val Clipboard = Value("Clipboard") + + val Analyze = Value("Analyze") } \ No newline at end of file diff --git a/li/cil/oc/common/block/Cable.scala b/li/cil/oc/common/block/Cable.scala index 2127131a7..3689131cb 100644 --- a/li/cil/oc/common/block/Cable.scala +++ b/li/cil/oc/common/block/Cable.scala @@ -32,13 +32,8 @@ class Cable(val parent: SpecialDelegator) extends SpecialDelegate { // ----------------------------------------------------------------------- // - - override def setBlockBoundsBasedOnState(world: IBlockAccess, x: Int, y: Int, z: Int) { - val bounds = Cable.bounds(world, x, y, z) - parent.setBlockBounds( - bounds.minX.toFloat, bounds.minY.toFloat, bounds.minZ.toFloat, - bounds.maxX.toFloat, bounds.maxY.toFloat, bounds.maxZ.toFloat) + parent.setBlockBounds(Cable.bounds(world, x, y, z)) } } diff --git a/li/cil/oc/common/block/Case.scala b/li/cil/oc/common/block/Case.scala index 9e26f92d2..d117620ff 100644 --- a/li/cil/oc/common/block/Case.scala +++ b/li/cil/oc/common/block/Case.scala @@ -1,8 +1,7 @@ package li.cil.oc.common.block -import li.cil.oc.common.GuiType +import li.cil.oc.Config import li.cil.oc.common.tileentity -import li.cil.oc.{Config, OpenComputers} import net.minecraft.client.renderer.texture.IconRegister import net.minecraft.entity.player.EntityPlayer import net.minecraft.util.Icon @@ -10,7 +9,7 @@ import net.minecraft.world.IBlockAccess import net.minecraft.world.World import net.minecraftforge.common.ForgeDirection -class Case(val parent: SimpleDelegator) extends SimpleDelegate { +class Case(val parent: SimpleDelegator) extends Computer with SimpleDelegate { val unlocalizedName = "Case" // ----------------------------------------------------------------------- // @@ -52,48 +51,10 @@ class Case(val parent: SimpleDelegator) extends SimpleDelegate { // ----------------------------------------------------------------------- // - override def hasTileEntity = true - override def createTileEntity(world: World) = Some(new tileentity.Case(world.isRemote)) // ----------------------------------------------------------------------- // - override def canConnectRedstone(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = - world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Case].canConnectRedstone(side) - - override def isProvidingStrongPower(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = - isProvidingWeakPower(world, x, y, z, side) - - override def isProvidingWeakPower(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = - world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Case].output(side) - - override def onBlockPreDestroy(world: World, x: Int, y: Int, z: Int) = - if (!world.isRemote) world.getBlockTileEntity(x, y, z) match { - case computer: tileentity.Case => - computer.instance.stop() - computer.dropContent(world, x, y, z) - case _ => // Ignore. - } - - override def onBlockActivated(world: World, x: Int, y: Int, z: Int, player: EntityPlayer, - side: ForgeDirection, hitX: Float, hitY: Float, hitZ: Float) = { - if (!player.isSneaking) { - // Start the computer if it isn't already running and open the GUI. - if (!world.isRemote) { - world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Case].instance.start() - } - player.openGui(OpenComputers, GuiType.Case.id, world, x, y, z) - true - } - else false - } - - override def onNeighborBlockChange(world: World, x: Int, y: Int, z: Int, blockId: Int) = - world.getBlockTileEntity(x, y, z) match { - case computer: tileentity.Case => computer.checkRedstoneInputChanged() - case _ => // Ignore. - } - // TODO do we have to manually sync the client since we can only check this on the server side? override def onBlockRemovedBy(world: World, x: Int, y: Int, z: Int, player: EntityPlayer) = world.getBlockTileEntity(x, y, z) match { diff --git a/li/cil/oc/common/block/Computer.scala b/li/cil/oc/common/block/Computer.scala new file mode 100644 index 000000000..532315610 --- /dev/null +++ b/li/cil/oc/common/block/Computer.scala @@ -0,0 +1,47 @@ +package li.cil.oc.common.block + +import li.cil.oc.OpenComputers +import li.cil.oc.common.{GuiType, tileentity} +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.world.{World, IBlockAccess} +import net.minecraftforge.common.ForgeDirection + +abstract class Computer extends Delegate { + override def hasTileEntity = true + + override def canConnectRedstone(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = + world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Computer].isOutputEnabled + + override def isProvidingStrongPower(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = + isProvidingWeakPower(world, x, y, z, side) + + override def isProvidingWeakPower(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = + world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Computer].output(side) + + override def onBlockPreDestroy(world: World, x: Int, y: Int, z: Int) = + if (!world.isRemote) world.getBlockTileEntity(x, y, z) match { + case computer: tileentity.Computer => + computer.instance.stop() + computer.dropContent(world, x, y, z) + case _ => // Ignore. + } + + override def onBlockActivated(world: World, x: Int, y: Int, z: Int, player: EntityPlayer, + side: ForgeDirection, hitX: Float, hitY: Float, hitZ: Float) = { + if (!player.isSneaking) { + // Start the computer if it isn't already running and open the GUI. + if (!world.isRemote) { + world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Computer].instance.start() + } + player.openGui(OpenComputers, GuiType.Case.id, world, x, y, z) + true + } + else false + } + + override def onNeighborBlockChange(world: World, x: Int, y: Int, z: Int, blockId: Int) = + world.getBlockTileEntity(x, y, z) match { + case computer: tileentity.Computer => computer.checkRedstoneInputChanged() + case _ => // Ignore. + } +} diff --git a/li/cil/oc/common/block/Delegator.scala b/li/cil/oc/common/block/Delegator.scala index 128ca5e1e..6d1ffb5ad 100644 --- a/li/cil/oc/common/block/Delegator.scala +++ b/li/cil/oc/common/block/Delegator.scala @@ -13,6 +13,7 @@ import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.{EnumCreatureType, Entity, EntityLivingBase} import net.minecraft.item.ItemStack import net.minecraft.tileentity.TileEntity +import net.minecraft.util.AxisAlignedBB import net.minecraft.world.IBlockAccess import net.minecraft.world.World import net.minecraftforge.common.ForgeDirection @@ -284,6 +285,12 @@ class Delegator[Child <: Delegate](id: Int, name: String) extends Block(id, Mate case _ => false } + def setBlockBounds(bounds: AxisAlignedBB) { + setBlockBounds( + bounds.minX.toFloat, bounds.minY.toFloat, bounds.minZ.toFloat, + bounds.maxX.toFloat, bounds.maxY.toFloat, bounds.maxZ.toFloat) + } + override def setBlockBoundsBasedOnState(world: IBlockAccess, x: Int, y: Int, z: Int) = subBlock(world, x, y, z) match { case Some(subBlock) => subBlock.setBlockBoundsBasedOnState(world, x, y, z) diff --git a/li/cil/oc/common/block/Robot.scala b/li/cil/oc/common/block/Robot.scala index 7cc76b833..9558b149f 100644 --- a/li/cil/oc/common/block/Robot.scala +++ b/li/cil/oc/common/block/Robot.scala @@ -1,14 +1,19 @@ package li.cil.oc.common.block import li.cil.oc.common.tileentity -import net.minecraft.world.World +import net.minecraft.world.{IBlockAccess, World} -class Robot(val parent: SpecialDelegator) extends SpecialDelegate { +class Robot(val parent: SpecialDelegator) extends Computer with SpecialDelegate { val unlocalizedName = "Robot" + override def createTileEntity(world: World) = Some(new tileentity.Robot) + // ----------------------------------------------------------------------- // - override def hasTileEntity = true - - override def createTileEntity(world: World) = Some(new tileentity.Robot) +// override def setBlockBoundsBasedOnState(world: IBlockAccess, x: Int, y: Int, z: Int) { +// world.getBlockTileEntity(x, y, z) match { +// case robot: tileentity.Robot => parent.setBlockBounds(robot.bounds) +// case _ => super.setBlockBoundsBasedOnState(world, x, y, z) +// } +// } } diff --git a/li/cil/oc/common/component/Buffer.scala b/li/cil/oc/common/component/Buffer.scala index 3182b542b..3ad8206d3 100644 --- a/li/cil/oc/common/component/Buffer.scala +++ b/li/cil/oc/common/component/Buffer.scala @@ -2,10 +2,12 @@ package li.cil.oc.common.component import li.cil.oc.api.network.Visibility import li.cil.oc.common.component +import li.cil.oc.common.tileentity import li.cil.oc.common.tileentity.TileEntity import li.cil.oc.util.{Persistable, PackedColor, TextBuffer} import li.cil.oc.{api, Config} import net.minecraft.nbt.NBTTagCompound +import scala.collection.convert.WrapAsScala._ class Buffer(val owner: Buffer.Environment) extends Persistable { val buffer = new TextBuffer(maxResolution, maxDepth) @@ -99,6 +101,22 @@ class Buffer(val owner: Buffer.Environment) extends Persistable { } override def save(nbt: NBTTagCompound) = { + // Happy thread synchronization hack! Here's the problem: GPUs allow direct + // calls for modifying screens to give a more responsive experience. This + // causes the following problem: when saving, if the screen is saved first, + // then the executor runs in parallel and changes the screen *before* the + // server thread begins saving that computer, the saved computer will think + // it changed the screen, although the saved screen wasn't. To avoid that we + // wait for all computers the screen is connected to to finish their current + // execution and pausing them (which will make them resume in the next tick + // when their update() runs). + if (owner.node.network != null) { + for (node <- owner.node.reachableNodes) node.host match { + case computer: tileentity.Computer => computer.instance.pause() + case _ => + } + } + val screenNbt = new NBTTagCompound buffer.save(screenNbt) nbt.setCompoundTag(Config.namespace + "screen", screenNbt) diff --git a/li/cil/oc/common/tileentity/Case.scala b/li/cil/oc/common/tileentity/Case.scala index 340076f8c..7228797bc 100644 --- a/li/cil/oc/common/tileentity/Case.scala +++ b/li/cil/oc/common/tileentity/Case.scala @@ -5,7 +5,6 @@ import li.cil.oc.api.driver.Slot import li.cil.oc.server.component import li.cil.oc.server.driver.Registry import net.minecraft.item.ItemStack -import net.minecraftforge.common.ForgeDirection class Case(isClient: Boolean) extends Computer { def this() = this(false) @@ -16,12 +15,6 @@ class Case(isClient: Boolean) extends Computer { // ----------------------------------------------------------------------- // - override def updateEntity() { - super.updateEntity() - } - - // ----------------------------------------------------------------------- // - def getInvName = Config.namespace + "container.Case" def getSizeInventory = 8 @@ -34,8 +27,4 @@ class Case(isClient: Boolean) extends Computer { case (6 | 7, Some(driver)) => driver.slot(item) == Slot.HardDiskDrive case _ => false // Invalid slot. } - - // ----------------------------------------------------------------------- // - - def canConnectRedstone(side: ForgeDirection) = isOutputEnabled } \ No newline at end of file diff --git a/li/cil/oc/common/tileentity/Inventory.scala b/li/cil/oc/common/tileentity/Inventory.scala index 772d4f3d8..2f030b46f 100644 --- a/li/cil/oc/common/tileentity/Inventory.scala +++ b/li/cil/oc/common/tileentity/Inventory.scala @@ -54,7 +54,7 @@ trait Inventory extends TileEntity with IInventory with Persistable { def isUseableByPlayer(player: EntityPlayer) = world.getBlockTileEntity(x, y, z) match { - case t: TileEntity if t == this => player.getDistanceSq(x + 0.5, y + 0.5, z + 0.5) < 64 + case t: TileEntity if t == this => player.getDistanceSq(x + 0.5, y + 0.5, z + 0.5) <= 64 case _ => false } diff --git a/li/cil/oc/common/tileentity/Keyboard.scala b/li/cil/oc/common/tileentity/Keyboard.scala index 8a8383e30..cf05cb166 100644 --- a/li/cil/oc/common/tileentity/Keyboard.scala +++ b/li/cil/oc/common/tileentity/Keyboard.scala @@ -73,7 +73,7 @@ class Keyboard extends Environment with Rotatable { private def isUseableByPlayer(p: EntityPlayer) = world.getBlockTileEntity(x, y, z) == this && - p.getDistanceSq(x + 0.5, y + 0.5, z + 0.5) < 64 + p.getDistanceSq(x + 0.5, y + 0.5, z + 0.5) <= 64 } object Keyboard extends IPlayerTracker { diff --git a/li/cil/oc/common/tileentity/PowerDistributor.scala b/li/cil/oc/common/tileentity/PowerDistributor.scala index a2a869f75..1a24a2471 100644 --- a/li/cil/oc/common/tileentity/PowerDistributor.scala +++ b/li/cil/oc/common/tileentity/PowerDistributor.scala @@ -47,36 +47,42 @@ class PowerDistributor extends Environment with Analyzable { // ----------------------------------------------------------------------- // def changeBuffer(delta: Double): Boolean = { - if (delta != 0) { + if (delta != 0) this.synchronized { val oldBuffer = globalBuffer globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize if (globalBuffer != oldBuffer) { dirty = true if (delta < 0) { var remaining = -delta - for (connector <- buffers if connector.localBuffer > 0) { - if (connector.localBuffer < remaining) { - remaining -= connector.localBuffer - connector.localBuffer = 0 - } - else { - connector.changeBuffer(-remaining) - return true - } + for (connector <- buffers) { + connector.synchronized(if (connector.localBuffer > 0) { + connector.dirty = true + if (connector.localBuffer < remaining) { + remaining -= connector.localBuffer + connector.localBuffer = 0 + } + else { + connector.localBuffer -= remaining + return true + } + }) } } else if (delta > 0) { var remaining = delta - for (connector <- buffers if connector.localBuffer < connector.localBufferSize) { - val space = connector.localBufferSize - connector.localBuffer - if (space < remaining) { - remaining -= space - connector.localBuffer = connector.localBufferSize - } - else { - connector.changeBuffer(remaining) - return true - } + for (connector <- buffers) { + connector.synchronized(if (connector.localBuffer < connector.localBufferSize) { + connector.dirty = true + val space = connector.localBufferSize - connector.localBuffer + if (space < remaining) { + remaining -= space + connector.localBuffer = connector.localBufferSize + } + else { + connector.localBuffer += remaining + return true + } + }) } } } @@ -106,10 +112,11 @@ class PowerDistributor extends Environment with Analyzable { super.onConnect(node) if (node == this.node) { for (node <- node.network.nodes) node match { - case connector: Connector if connector.localBufferSize > 0 => + case connector: Connector if connector.localBufferSize > 0 => this.synchronized { buffers += connector globalBuffer += connector.localBuffer globalBufferSize += connector.localBufferSize + } case _ => node.host match { case distributor: PowerDistributor => distributors += distributor case _ => @@ -118,11 +125,12 @@ class PowerDistributor extends Environment with Analyzable { dirty = true } else node match { - case connector: Connector => + case connector: Connector => this.synchronized { buffers += connector globalBuffer += connector.localBuffer globalBufferSize += connector.localBufferSize dirty = true + } case _ => node.host match { case distributor: PowerDistributor => distributors += distributor case _ => @@ -132,7 +140,7 @@ class PowerDistributor extends Environment with Analyzable { override def onDisconnect(node: Node) { super.onDisconnect(node) - if (node == this.node) { + if (node == this.node) this.synchronized { buffers.clear() distributors.clear() globalBuffer = 0 @@ -140,11 +148,12 @@ class PowerDistributor extends Environment with Analyzable { average = -1 } else node match { - case connector: Connector => + case connector: Connector => this.synchronized { buffers -= connector globalBuffer -= connector.localBuffer globalBufferSize -= connector.localBufferSize dirty = true + } case _ => node.host match { case distributor: PowerDistributor => distributors -= distributor case _ => @@ -163,7 +172,7 @@ class PowerDistributor extends Environment with Analyzable { }) average = if (globalBufferSize > 0) globalBuffer / globalBufferSize else 0 val shouldSend = (lastSentAverage - average).abs > 0.05 - for (distributor <- distributors) { + for (distributor <- distributors) distributor.synchronized { distributor.dirty = false distributor.globalBuffer = sumBuffer distributor.globalBufferSize = sumBufferSize diff --git a/li/cil/oc/common/tileentity/Robot.scala b/li/cil/oc/common/tileentity/Robot.scala index 70552ac54..2b52ff042 100644 --- a/li/cil/oc/common/tileentity/Robot.scala +++ b/li/cil/oc/common/tileentity/Robot.scala @@ -1,15 +1,88 @@ package li.cil.oc.common.tileentity +import li.cil.oc.Config +import li.cil.oc.api.driver.Slot +import li.cil.oc.api.network.{Context, Arguments, LuaCallback} import li.cil.oc.server.component +import li.cil.oc.server.driver.Registry import net.minecraft.item.ItemStack -class Robot extends Computer with ComponentInventory with Rotatable with Redstone { +class Robot(isClient: Boolean) extends Computer { + def this() = this(false) - val instance = if (true) null else new component.Computer(this) + // ----------------------------------------------------------------------- // - def getInvName = "" + val instance = if (isClient) null else new component.Computer(this) - def getSizeInventory = 0 + // ----------------------------------------------------------------------- // - def isItemValidForSlot(i: Int, itemstack: ItemStack) = false + //def bounds = + + // ----------------------------------------------------------------------- // + + @LuaCallback("attack") + def attack(context: Context, args: Arguments): Array[AnyRef] = { + // Attack with equipped tool. + val side = args.checkInteger(0) + null + } + + @LuaCallback("use") + def use(context: Context, args: Arguments): Array[AnyRef] = { + // Use equipped tool (e.g. dig, chop, till). + val side = args.checkInteger(0) + val sneaky = args.checkBoolean(1) + null + } + + // ----------------------------------------------------------------------- // + + @LuaCallback("check") + def check(context: Context, args: Arguments): Array[AnyRef] = { + // Test for blocks or entities. + null + } + + @LuaCallback("collect") + def collect(context: Context, args: Arguments): Array[AnyRef] = { + // Pick up items lying around. + null + } + + @LuaCallback("compare") + def compare(context: Context, args: Arguments): Array[AnyRef] = { + // Compare block to item selected in inventory. + null + } + + @LuaCallback("drop") + def drop(context: Context, args: Arguments): Array[AnyRef] = { + // Drop items from inventory. + null + } + + @LuaCallback("move") + def move(context: Context, args: Arguments): Array[AnyRef] = { + // Try to move in the specified direction. + null + } + + @LuaCallback("place") + def place(context: Context, args: Arguments): Array[AnyRef] = { + // Place block item selected in inventory. + null + } + + // ----------------------------------------------------------------------- // + + def getInvName = Config.namespace + "container.Robot" + + def getSizeInventory = 12 + + def isItemValidForSlot(slot: Int, item: ItemStack) = (slot, Registry.driverFor(item)) match { + case (0, Some(driver)) => driver.slot(item) == Slot.Tool + case (1 | 2, Some(driver)) => driver.slot(item) == Slot.Card + case (3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11, _) => true // Normal inventory. + case _ => false // Invalid slot. + } } diff --git a/li/cil/oc/server/component/Carriage.scala b/li/cil/oc/server/component/Carriage.scala index 5ad92aaf9..831a5f501 100644 --- a/li/cil/oc/server/component/Carriage.scala +++ b/li/cil/oc/server/component/Carriage.scala @@ -23,28 +23,28 @@ class Carriage(controller: AnyRef) extends ManagedComponent { // ----------------------------------------------------------------------- // - @LuaCallback(value = "move", direct = true) - def move(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { + @LuaCallback("move") + def move(context: Context, args: Arguments): Array[AnyRef] = { direction = checkDirection(args) simulating = if (args.count > 1) args.checkBoolean(1) else false shouldMove = true result(true) } - @LuaCallback(value = "simulate", direct = true) - def simulate(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { + @LuaCallback("simulate") + def simulate(context: Context, args: Arguments): Array[AnyRef] = { direction = checkDirection(args) simulating = true shouldMove = true result(true) } - @LuaCallback(value = "getAnchored", direct = true) + @LuaCallback("getAnchored") def getAnchored(context: Context, args: Arguments): Array[AnyRef] = - this.synchronized(result(anchored)) + result(anchored) - @LuaCallback(value = "setAnchored", direct = true) - def setAnchored(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { + @LuaCallback("setAnchored") + def setAnchored(context: Context, args: Arguments): Array[AnyRef] = { anchored = args.checkBoolean(0) result(anchored) } @@ -72,7 +72,7 @@ class Carriage(controller: AnyRef) extends ManagedComponent { override def update() { super.update() - if (shouldMove) this.synchronized { + if (shouldMove) { shouldMove = false moving = true try { @@ -104,7 +104,7 @@ class Carriage(controller: AnyRef) extends ManagedComponent { // ----------------------------------------------------------------------- // - override def save(nbt: NBTTagCompound) { + override def save(nbt: NBTTagCompound) = { super.save(nbt) nbt.setBoolean("moving", moving) nbt.setBoolean("anchored", anchored) diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index 7b1f3e2b7..d989d70c7 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -97,6 +97,10 @@ class Computer(val owner: tileentity.Computer) extends Persistable with Runnable true }) + def pause() = if (state.synchronized(isRunning && !isStopping)) { + this.synchronized(if (!isStopping) state.push(Computer.State.Paused)) + } + def stop() = state.synchronized(state.top match { case Computer.State.Stopped | Computer.State.Stopping => false case _ => state.push(Computer.State.Stopping); true @@ -104,6 +108,8 @@ class Computer(val owner: tileentity.Computer) extends Persistable with Runnable def isRunning = state.synchronized(state.top != Computer.State.Stopped) + def isStopping = state.synchronized(state.top == Computer.State.Stopping) + // ----------------------------------------------------------------------- // def isUser(player: String) = !Config.canComputersBeOwned || @@ -245,6 +251,11 @@ class Computer(val owner: tileentity.Computer) extends Persistable with Runnable assert(lua.getTop == 2) assert(lua.isThread(1)) assert(lua.isFunction(2)) + // Clear direct call limits again, just to be on the safe side... + // Theoretically it'd be possible for the executor to do some direct + // calls between the clear and the state check, which could in turn + // make this synchronized call fail due the limit still being maxed. + callCounts.clear() // We switch into running state, since we'll behave as though the call // were performed from our executor thread. switchTo(Computer.State.Running) @@ -931,18 +942,8 @@ class Computer(val owner: tileentity.Computer) extends Persistable with Runnable lua.setGlobal("component") - // Run the boot script. This sets up the permanent value tables as - // well as making the functions used for persisting/unpersisting - // available as globals. It also wraps the message sending functions - // so that they yield a closure doing the actual call so that that - // message call can be performed in a synchronized fashion. - // lua.load(classOf[Computer].getResourceAsStream(Config.scriptPath + "boot.lua"), "=boot", "t") - // lua.call(0, 0) initPerms() - // Load the basic kernel which sets up the sandbox, loads the init script - // and then runs it in a coroutine with a debug hook checking for - // timeouts. lua.load(classOf[Computer].getResourceAsStream(Config.scriptPath + "kernel.lua"), "=kernel", "t") lua.newThread() // Left as the first value on the stack. @@ -1066,13 +1067,16 @@ class Computer(val owner: tileentity.Computer) extends Persistable with Runnable future = None val enterState = state.synchronized { + if (state.top == Computer.State.Stopped || + state.top == Computer.State.Stopping || + state.top == Computer.State.Paused) { + return + } // See if the game appears to be paused, in which case we also pause. if (System.currentTimeMillis - lastUpdate > 100) { state.push(Computer.State.Paused) return } - if (state.top == Computer.State.Stopping) - return switchTo(Computer.State.Running) } diff --git a/li/cil/oc/server/component/Filesystem.scala b/li/cil/oc/server/component/Filesystem.scala index f9d1fa472..8b2d2b420 100644 --- a/li/cil/oc/server/component/Filesystem.scala +++ b/li/cil/oc/server/component/Filesystem.scala @@ -19,11 +19,10 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma // ----------------------------------------------------------------------- // @LuaCallback(value = "getLabel", direct = true) - def getLabel(context: Context, args: Arguments): Array[AnyRef] = - this.synchronized(result(label.getLabel)) + def getLabel(context: Context, args: Arguments): Array[AnyRef] = result(label.getLabel) - @LuaCallback(value = "setLabel", direct = true) - def setLabel(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { + @LuaCallback("setLabel") + def setLabel(context: Context, args: Arguments): Array[AnyRef] = { if (label == null) throw new Exception("filesystem does not support labeling") if (args.checkAny(0) == null) label.setLabel(null) else label.setLabel(args.checkString(0)) @@ -31,12 +30,12 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma } @LuaCallback(value = "isReadOnly", direct = true) - def isReadOnly(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def isReadOnly(context: Context, args: Arguments): Array[AnyRef] = { result(fileSystem.isReadOnly) } @LuaCallback(value = "spaceTotal", direct = true) - def spaceTotal(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def spaceTotal(context: Context, args: Arguments): Array[AnyRef] = { val space = fileSystem.spaceTotal if (space < 0) Array("unlimited") @@ -45,59 +44,59 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma } @LuaCallback(value = "spaceUsed", direct = true) - def spaceUsed(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def spaceUsed(context: Context, args: Arguments): Array[AnyRef] = { result(fileSystem.spaceUsed) } @LuaCallback(value = "exists", direct = true) - def exists(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def exists(context: Context, args: Arguments): Array[AnyRef] = { result(fileSystem.exists(clean(args.checkString(0)))) } @LuaCallback(value = "size", direct = true) - def size(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def size(context: Context, args: Arguments): Array[AnyRef] = { result(fileSystem.size(clean(args.checkString(0)))) } @LuaCallback(value = "isDirectory", direct = true) - def isDirectory(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def isDirectory(context: Context, args: Arguments): Array[AnyRef] = { result(fileSystem.isDirectory(clean(args.checkString(0)))) } @LuaCallback(value = "lastModified", direct = true) - def lastModified(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def lastModified(context: Context, args: Arguments): Array[AnyRef] = { result(fileSystem.lastModified(clean(args.checkString(0)))) } @LuaCallback("list") - def list(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def list(context: Context, args: Arguments): Array[AnyRef] = { Option(fileSystem.list(clean(args.checkString(0)))) match { - case Some(list) => Array(list) - case _ => null - } + case Some(list) => Array(list) + case _ => null + } } @LuaCallback("makeDirectory") - def makeDirectory(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def makeDirectory(context: Context, args: Arguments): Array[AnyRef] = { def recurse(path: String): Boolean = !fileSystem.exists(path) && (fileSystem.makeDirectory(path) || (recurse(path.split("/").dropRight(1).mkString("/")) && fileSystem.makeDirectory(path))) result(recurse(clean(args.checkString(0)))) } @LuaCallback("remove") - def remove(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def remove(context: Context, args: Arguments): Array[AnyRef] = { def recurse(parent: String): Boolean = (!fileSystem.isDirectory(parent) || fileSystem.list(parent).forall(child => recurse(parent + "/" + child))) && fileSystem.delete(parent) result(recurse(clean(args.checkString(0)))) } @LuaCallback("rename") - def rename(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def rename(context: Context, args: Arguments): Array[AnyRef] = { result(fileSystem.rename(clean(args.checkString(0)), clean(args.checkString(1)))) } @LuaCallback("close") - def close(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def close(context: Context, args: Arguments): Array[AnyRef] = { val handle = args.checkInteger(0) Option(fileSystem.getHandle(handle)) match { case Some(file) => @@ -111,8 +110,8 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma } @LuaCallback("open") - def open(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { - if (owners.get(context.address).fold(false)(_.size >= Config.maxHandles)) + def open(context: Context, args: Arguments): Array[AnyRef] = { + if (owners.get(context.address).fold(false)(_.size >= Config.maxHandles)) throw new IOException("too many open handles") else { val path = args.checkString(0) @@ -126,7 +125,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma } @LuaCallback("read") - def read(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def read(context: Context, args: Arguments): Array[AnyRef] = { val handle = args.checkInteger(0) val n = args.checkInteger(1) checkOwner(context.address, handle) @@ -157,7 +156,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma } @LuaCallback("seek") - def seek(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def seek(context: Context, args: Arguments): Array[AnyRef] = { val handle = args.checkInteger(0) val whence = args.checkString(1) val offset = args.checkInteger(2) @@ -176,7 +175,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma } @LuaCallback("write") - def write(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { + def write(context: Context, args: Arguments): Array[AnyRef] = { val handle = args.checkInteger(0) val value = args.checkByteArray(1) if (!node.changeBuffer(-Config.hddWriteCost * value.length)) { @@ -196,7 +195,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma message.data match { case Array() if message.name == "computer.stopped" || message.name == "computer.started" => owners.get(message.source.address) match { - case Some(set) => fileSystem.synchronized { + case Some(set) => { set.foreach(handle => Option(fileSystem.getHandle(handle)) match { case Some(file) => file.close() case _ => // Invalid handle... huh. @@ -211,10 +210,10 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma override def onDisconnect(node: Node) { super.onDisconnect(node) - if (node == this.node) fileSystem.synchronized { + if (node == this.node) { fileSystem.close() } - else if (owners.contains(node.address)) fileSystem.synchronized { + else if (owners.contains(node.address)) { for (handle <- owners(node.address)) { Option(fileSystem.getHandle(handle)) match { case Some(file) => file.close() diff --git a/li/cil/oc/server/component/GraphicsCard.scala b/li/cil/oc/server/component/GraphicsCard.scala index 4790c7c2e..b39eab1d3 100644 --- a/li/cil/oc/server/component/GraphicsCard.scala +++ b/li/cil/oc/server/component/GraphicsCard.scala @@ -21,7 +21,15 @@ abstract class GraphicsCard extends ManagedComponent { private var screenInstance: Option[Buffer] = None - private def screen(f: (Buffer) => Array[AnyRef]) = this.synchronized { + private def screen(f: (Buffer) => Array[AnyRef]) = screenInstance match { + case Some(screen) => screen.synchronized(f(screen)) + case _ => Array(Unit, "no screen") + } + + // ----------------------------------------------------------------------- // + + override def update() { + super.update() if (screenInstance.isEmpty && screenAddress.isDefined) { Option(node.network.node(screenAddress.get)) match { case Some(node: Node) if node.host.isInstanceOf[Buffer.Environment] => @@ -35,22 +43,16 @@ abstract class GraphicsCard extends ManagedComponent { screenAddress = None } } - screenInstance match { - case Some(screen) => f(screen) - case _ => Array(Unit, "no screen") - } } - // ----------------------------------------------------------------------- // - @LuaCallback("bind") - def bind(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { + def bind(context: Context, args: Arguments): Array[AnyRef] = { val address = args.checkString(0) node.network.node(address) match { case null => Array(Unit, "invalid address") - case node: Node if node.host.isInstanceOf[Buffer.Environment] => + case node: Node if node.host.isInstanceOf[Buffer.Environment] => { screenAddress = Option(address) - screenInstance = None + screenInstance = Some(node.host.asInstanceOf[Buffer.Environment].instance) screen(s => { val (gmw, gmh) = maxResolution val (smw, smh) = s.maxResolution @@ -60,6 +62,7 @@ abstract class GraphicsCard extends ManagedComponent { s.background = 0x000000 result(true) }) + } case _ => Array(Unit, "not a screen") } } @@ -220,6 +223,18 @@ abstract class GraphicsCard extends ManagedComponent { object GraphicsCard { + // IMPORTANT: usually methods with side effects should *not* be direct + // callbacks to avoid the massive headache synchronizing them ensues, in + // particular when it comes to world saving. I'm making an exception for + // screens, though since they'd be painfully sluggish otherwise. This also + // means we have to use a somewhat nasty trick in common.component.Buffer's + // save function: we wait for all computers in the same network to finish + // their current execution and then pause them, to ensure the state of the + // buffer is "clean", meaning the computer has the correct state when it is + // saved in turn. If we didn't, a computer might change a screen after it was + // saved, but before the computer was saved, leading to mismatching states in + // the save file - a Bad Thing (TM). + class Tier1 extends GraphicsCard { val maxDepth = Config.screenDepthsByTier(0) val maxResolution = Config.screenResolutionsByTier(0) diff --git a/li/cil/oc/server/component/NetworkCard.scala b/li/cil/oc/server/component/NetworkCard.scala index 5f3343538..878334a2f 100644 --- a/li/cil/oc/server/component/NetworkCard.scala +++ b/li/cil/oc/server/component/NetworkCard.scala @@ -16,13 +16,13 @@ class NetworkCard extends ManagedComponent { // ----------------------------------------------------------------------- // @LuaCallback("open") - def open(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { + def open(context: Context, args: Arguments): Array[AnyRef] = { val port = checkPort(args.checkInteger(0)) result(openPorts.add(port)) } @LuaCallback("close") - def close(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { + def close(context: Context, args: Arguments): Array[AnyRef] = { if (args.count == 0) { openPorts.clear() result(true) @@ -34,7 +34,7 @@ class NetworkCard extends ManagedComponent { } @LuaCallback(value = "isOpen", direct = true) - def isOpen(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { + def isOpen(context: Context, args: Arguments): Array[AnyRef] = { val port = checkPort(args.checkInteger(0)) result(openPorts.contains(port)) } @@ -66,7 +66,7 @@ class NetworkCard extends ManagedComponent { } } - override def onMessage(message: Message) = this.synchronized { + override def onMessage(message: Message) = { super.onMessage(message) if ((message.name == "computer.stopped" || message.name == "computer.started") && node.isNeighborOf(message.source)) openPorts.clear() diff --git a/li/cil/oc/server/component/WirelessNetworkCard.scala b/li/cil/oc/server/component/WirelessNetworkCard.scala index 2909720e0..9bbaacb86 100644 --- a/li/cil/oc/server/component/WirelessNetworkCard.scala +++ b/li/cil/oc/server/component/WirelessNetworkCard.scala @@ -22,15 +22,15 @@ class WirelessNetworkCard(val owner: TileEntity) extends NetworkCard { @LuaCallback(value = "getStrength", direct = true) def getStrength(context: Context, args: Arguments): Array[AnyRef] = result(strength) - @LuaCallback(value = "setStrength", direct = true) - def setStrength(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { + @LuaCallback("setStrength") + def setStrength(context: Context, args: Arguments): Array[AnyRef] = { strength = args.checkDouble(0) max 0 min Config.maxWirelessRange result(strength) } override def isWireless(context: Context, args: Arguments): Array[AnyRef] = result(true) - override def send(context: Context, args: Arguments) = this.synchronized { + override def send(context: Context, args: Arguments) = { val address = args.checkString(0) val port = checkPort(args.checkInteger(1)) if (strength > 0) { @@ -44,7 +44,7 @@ class WirelessNetworkCard(val owner: TileEntity) extends NetworkCard { super.send(context, args) } - override def broadcast(context: Context, args: Arguments) = this.synchronized { + override def broadcast(context: Context, args: Arguments) = { val port = checkPort(args.checkInteger(0)) if (strength > 0) { checkPower() diff --git a/li/cil/oc/server/network/Connector.scala b/li/cil/oc/server/network/Connector.scala index 48b39345d..cf21b04f3 100644 --- a/li/cil/oc/server/network/Connector.scala +++ b/li/cil/oc/server/network/Connector.scala @@ -26,21 +26,24 @@ trait Connector extends Node with network.Connector with Persistable { // ----------------------------------------------------------------------- // def changeBuffer(delta: Double) = if (delta != 0) { - val oldBuffer = localBuffer - localBuffer = localBuffer + delta - val ok = if (localBuffer < 0) { - val remaining = localBuffer - localBuffer = 0 - distributor.fold(false)(_.changeBuffer(remaining)) + val remaining = this.synchronized { + val oldBuffer = localBuffer + localBuffer = localBuffer + delta + val remaining = if (localBuffer < 0) { + val remaining = localBuffer + localBuffer = 0 + remaining + } + else if (localBuffer > localBufferSize) { + val remaining = localBuffer - localBufferSize + localBuffer = localBufferSize + remaining + } + else 0 + dirty ||= (localBuffer != oldBuffer) + remaining } - else if (localBuffer > localBufferSize) { - val remaining = localBuffer - localBufferSize - localBuffer = localBufferSize - distributor.fold(false)(_.changeBuffer(remaining)) - } - else true - dirty ||= (localBuffer != oldBuffer) - ok || Config.ignorePower + distributor.fold(remaining == 0)(_.changeBuffer(remaining)) || Config.ignorePower } else true // ----------------------------------------------------------------------- // @@ -59,7 +62,7 @@ trait Connector extends Node with network.Connector with Persistable { super.onDisconnect(node) } - private def findDistributor() { + private def findDistributor() = { distributor = reachableNodes.find(_.host.isInstanceOf[PowerDistributor]).fold(None: Option[PowerDistributor])(n => Some(n.host.asInstanceOf[PowerDistributor])) } diff --git a/li/cil/oc/server/network/Network.scala b/li/cil/oc/server/network/Network.scala index f52ed1bfc..f6e1c79c4 100644 --- a/li/cil/oc/server/network/Network.scala +++ b/li/cil/oc/server/network/Network.scala @@ -12,7 +12,7 @@ import scala.collection.JavaConverters._ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -class Network private(private val data: mutable.Map[String, Network.Vertex] = mutable.Map.empty) { +private class Network private(private val data: mutable.Map[String, Network.Vertex] = mutable.Map.empty) { def this(node: MutableNode) = { this() addNew(node) @@ -25,7 +25,7 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu // ----------------------------------------------------------------------- // - def connect(nodeA: MutableNode, nodeB: MutableNode) = this.synchronized { + def connect(nodeA: MutableNode, nodeB: MutableNode) = { if (nodeA == nodeB) throw new IllegalArgumentException( "Cannot connect a node to itself.") @@ -59,7 +59,7 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu else add(oldNodeB, nodeA) } - def disconnect(nodeA: MutableNode, nodeB: MutableNode) = this.synchronized { + def disconnect(nodeA: MutableNode, nodeB: MutableNode) = { if (nodeA == nodeB) throw new IllegalArgumentException( "Cannot disconnect a node from itself.") @@ -85,7 +85,7 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu } } - def remove(node: MutableNode) = this.synchronized { + def remove(node: MutableNode) = { data.remove(node.address) match { case Some(entry) => { node.network = null @@ -105,14 +105,14 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu // ----------------------------------------------------------------------- // - def node(address: String) = this.synchronized { + def node(address: String) = { data.get(address) match { case Some(node) => node.data case _ => null } } - def nodes: Iterable[ImmutableNode] = this.synchronized(data.values.map(_.data)) + def nodes: Iterable[ImmutableNode] = data.values.map(_.data) def nodes(reference: ImmutableNode): Iterable[ImmutableNode] = { val referenceNeighbors = neighbors(reference).toSet @@ -120,7 +120,7 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu (node.reachability == Visibility.Neighbors && referenceNeighbors.contains(node)))) } - def neighbors(node: ImmutableNode): Iterable[ImmutableNode] = this.synchronized { + def neighbors(node: ImmutableNode): Iterable[ImmutableNode] = { data.get(node.address) match { case Some(n) => assert(n.data == node) @@ -131,7 +131,7 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu // ----------------------------------------------------------------------- // - def sendToAddress(source: ImmutableNode, target: String, name: String, args: AnyRef*) = this.synchronized { + def sendToAddress(source: ImmutableNode, target: String, name: String, args: AnyRef*) = { if (source.network != wrapper) throw new IllegalArgumentException("Source node must be in this network.") data.get(target) match { @@ -141,13 +141,13 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu } } - def sendToNeighbors(source: ImmutableNode, name: String, args: AnyRef*) = this.synchronized { + def sendToNeighbors(source: ImmutableNode, name: String, args: AnyRef*) = { if (source.network != wrapper) throw new IllegalArgumentException("Source node must be in this network.") send(source, neighbors(source).filter(_.reachability != Visibility.None), name, args: _*) } - def sendToReachable(source: ImmutableNode, name: String, args: AnyRef*) = this.synchronized { + def sendToReachable(source: ImmutableNode, name: String, args: AnyRef*) = { if (source.network != wrapper) throw new IllegalArgumentException("Source node must be in this network.") send(source, nodes(source), name, args: _*) @@ -386,8 +386,7 @@ object Network extends api.detail.NetworkAPI { def disconnect(nodeA: ImmutableNode, nodeB: ImmutableNode) = network.disconnect(nodeA.asInstanceOf[MutableNode], nodeB.asInstanceOf[MutableNode]) - def remove(node: ImmutableNode) = - network.remove(node.asInstanceOf[MutableNode]) + def remove(node: ImmutableNode) = network.remove(node.asInstanceOf[MutableNode]) def node(address: String) = network.node(address) diff --git a/li/cil/oc/server/network/Node.scala b/li/cil/oc/server/network/Node.scala index 6cbc5c120..2d41f82b1 100644 --- a/li/cil/oc/server/network/Node.scala +++ b/li/cil/oc/server/network/Node.scala @@ -48,8 +48,7 @@ trait Node extends api.network.Node with Persistable { def sendToReachable(name: String, data: AnyRef*) = if (network != null) network.sendToReachable(this, name, data: _*) - private def isInSameNetwork(other: ImmutableNode) = - network != null && network == other.network + private def isInSameNetwork(other: ImmutableNode) = network != null && network == other.network // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/util/RenderState.scala b/li/cil/oc/util/RenderState.scala index 7b982b9c4..e8a1b7576 100644 --- a/li/cil/oc/util/RenderState.scala +++ b/li/cil/oc/util/RenderState.scala @@ -1,13 +1,30 @@ package li.cil.oc.util +import li.cil.oc.OpenComputers import net.minecraft.client.renderer.OpenGlHelper import org.lwjgl.opengl._ +import org.lwjgl.util.glu.GLU object RenderState { val arb = GLContext.getCapabilities.GL_ARB_multitexture && !GLContext.getCapabilities.OpenGL13 private val canUseBlendColor = GLContext.getCapabilities.OpenGL14 + def checkError(where: String) { + val error = GL11.glGetError + if (error != 0) { + OpenComputers.log.warning("GL ERROR @ " + where + ": " + GLU.gluErrorString(error)) + } + } + + def compilingDisplayList = { + if (GL11.glGetInteger(GL11.GL_LIST_INDEX) != 0) { + val mode = GL11.glGetInteger(GL11.GL_LIST_MODE) + mode == GL11.GL_COMPILE || mode == GL11.GL_COMPILE_AND_EXECUTE + } + else false + } + def disableLighting() { GL11.glDisable(GL11.GL_LIGHTING) if (arb) {