messed with synchronization of network a bit, mostly removing the synchronizations, meaning using the network in direct lua callbacks is *not safe* anymore. consuming / producing power however should be, that's synchronized (and hopefully won't deadlock, unless I forgot some case); fixed opengl errors when moving screens using RIM by checking if a display list is currently being compiled, and not trying to compile again if so, but simple rendering what we currently have. also reduced the keep-alive time of the cache a bit.

This commit is contained in:
Florian Nücke 2013-11-16 02:35:55 +01:00
parent 5faedd2525
commit c8bcc59936
29 changed files with 386 additions and 254 deletions

View File

@ -264,7 +264,7 @@ sandbox = {
(type(timeout) == "number" and timeout or math.huge) (type(timeout) == "number" and timeout or math.huge)
repeat repeat
local signal = table.pack(coroutine.yield(deadline - os.uptime())) 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) return table.unpack(signal, 1, signal.n)
end end
until os.uptime() >= deadline until os.uptime() >= deadline
@ -295,12 +295,13 @@ sandbox = {
return nil, reason return nil, reason
end end
local proxy = {address = address, type = type} local proxy = {address = address, type = type}
local methods = component.methods(address) local methods, reason = component.methods(address)
if methods then if not methods then
for method, direct in pairs(methods) do return nil, reason
proxy[method] = function(...) end
return invoke(direct, address, method, ...) for method, direct in pairs(methods) do
end proxy[method] = function(...)
return invoke(direct, address, method, ...)
end end
end end
return proxy return proxy

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

View File

@ -4,32 +4,36 @@ package li.cil.oc.api.driver;
* List of possible item component types. * List of possible item component types.
* <p/> * <p/>
* This is used to determine which item components may go into which slots in * 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 * a computer's inventory.
* inside the computer, not next to it.
*/ */
public enum Slot { 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 generating components, such as generators.
*/ */
Power, Power,
/** /**
* Memory extension components. * Tools that can be used by robots.
*/ */
Memory, Tool
/**
* Hard disk drives.
*/
HardDiskDrive,
/**
* Extension cards such as graphics or redstone cards.
*/
Card,
/**
* Floppy disks.
*/
Disk
} }

View File

@ -38,10 +38,9 @@ public @interface LuaCallback {
* This is mainly intended to allow functions to perform faster than when * This is mainly intended to allow functions to perform faster than when
* called 'synchronously' (where the call takes at least one server tick). * called 'synchronously' (where the call takes at least one server tick).
* <p/> * <p/>
* Note that {@link Network} interaction is mostly synchronized - i.e. the * Keep in mind that the node {@link Network} is <em>not</em> thread safe!
* operations on the network itself are: once you get some result you're * Be sure you know what you're doing if you're working with a node's
* <em>not</em> guaranteed that node you just fetched is still in the * network in a direct callback.
* network, for example!
*/ */
boolean direct() default false; boolean direct() default false;
@ -63,6 +62,9 @@ public @interface LuaCallback {
* via a direct call to {@link Component#invoke(String, Context, Object...)} * 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 * 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. * be invoked more often than this per tick, if different computers call it.
* <p/>
* 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; int limit() default Integer.MAX_VALUE;
} }

View File

@ -33,6 +33,10 @@ package li.cil.oc.api.network;
* nodes of the other network(s), when a network is split (all pairs).</li> * nodes of the other network(s), when a network is split (all pairs).</li>
* </ul> * </ul>
* <p/> * <p/>
* Note that network access is <em>not</em> thread safe! Networks should only
* be accessed from the main server thread.
*
* <p/>
* IMPORTANT: do *not* implement this interface yourself and create * IMPORTANT: do *not* implement this interface yourself and create
* instances of your own network implementation; this will lead to * instances of your own network implementation; this will lead to
* incompatibilities with the built-in network implementation (which can only * incompatibilities with the built-in network implementation (which can only

View File

@ -24,9 +24,10 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with
/** We cache the display lists for the screens we render for performance. */ /** We cache the display lists for the screens we render for performance. */
val cache = com.google.common.cache.CacheBuilder.newBuilder(). val cache = com.google.common.cache.CacheBuilder.newBuilder().
expireAfterAccess(5, TimeUnit.SECONDS). expireAfterAccess(2, TimeUnit.SECONDS).
removalListener(this). 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(). */ /** Used to pass the current screen along to call(). */
private var screen: Screen = null private var screen: Screen = null
@ -68,19 +69,18 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with
MonospaceFontRenderer.init(tileEntityRenderer.renderEngine) MonospaceFontRenderer.init(tileEntityRenderer.renderEngine)
val list = cache.get(screen, this) val list = cache.get(screen, this)
compile(list) compileOrDraw(list)
GL11.glCallList(list)
GL11.glPopMatrix() GL11.glPopMatrix()
GL11.glPopAttrib() GL11.glPopAttrib()
} }
private def compile(list: Int) = if (screen.hasChanged) { private def compileOrDraw(list: Int) = if (screen.hasChanged && !RenderState.compilingDisplayList) {
screen.hasChanged = false screen.hasChanged = false
val (sx, sy) = (screen.width, screen.height) val (sx, sy) = (screen.width, screen.height)
val (tw, th) = (sx * 16f, sy * 16f) val (tw, th) = (sx * 16f, sy * 16f)
GL11.glNewList(list, GL11.GL_COMPILE) GL11.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE)
screen.yaw match { screen.yaw match {
case ForgeDirection.WEST => GL11.glRotatef(-90, 0, 1, 0) case ForgeDirection.WEST => GL11.glRotatef(-90, 0, 1, 0)
@ -137,7 +137,10 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with
} }
GL11.glEndList() GL11.glEndList()
true
} }
else GL11.glCallList(list)
private def playerDistanceSq() = { private def playerDistanceSq() = {
val player = Minecraft.getMinecraft.thePlayer val player = Minecraft.getMinecraft.thePlayer
@ -191,12 +194,12 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with
def call = { def call = {
val list = GLAllocation.generateDisplayLists(1) val list = GLAllocation.generateDisplayLists(1)
screen.hasChanged = true // Force compilation. screen.hasChanged = true // Force compilation.
compile(list)
list list
} }
def onRemoval(e: RemovalNotification[TileEntity, Int]) = def onRemoval(e: RemovalNotification[TileEntity, Int]) {
GLAllocation.deleteDisplayLists(e.getValue) GLAllocation.deleteDisplayLists(e.getValue)
}
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
// ITickHandler // ITickHandler

View File

@ -1,7 +1,5 @@
package li.cil.oc.common package li.cil.oc.common
object GuiType extends Enumeration { object GuiType extends Enumeration {
val Case = Value("Case") val Case, DiskDrive, Screen = Value
val DiskDrive = Value("DiskDrive")
val Screen = Value("Screen")
} }

View File

@ -1,39 +1,21 @@
package li.cil.oc.common package li.cil.oc.common
object PacketType extends Enumeration { 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 ComputerStateRequest = Value("ComputerStateRequest")
val ComputerStateResponse = Value("ComputerStateResponse") val ComputerStateResponse = Value("ComputerStateResponse")
/** Sent by power distributors for client display of average buffer fill. */
val PowerStateRequest = Value("PowerStateRequest") val PowerStateRequest = Value("PowerStateRequest")
val PowerStateResponse = Value("PowerStateResponse") val PowerStateResponse = Value("PowerStateResponse")
/** Sent by redstone capable blocks (e.g. computers). */
val RedstoneStateRequest = Value("RedstoneStateRequest") val RedstoneStateRequest = Value("RedstoneStateRequest")
val RedstoneStateResponse = Value("RedstoneStateResponse") val RedstoneStateResponse = Value("RedstoneStateResponse")
/** Sent by rotatable tile entities to notify clients of changes. */
val RotatableStateRequest = Value("RotatableStateRequest") val RotatableStateRequest = Value("RotatableStateRequest")
val RotatableStateResponse = Value("RotatableStateResponse") 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 ScreenBufferRequest = Value("ScreenBufferRequest")
val ScreenBufferResponse = Value("ScreenBufferResponse") val ScreenBufferResponse = Value("ScreenBufferResponse")
/** These are sent from the server to the client for partial updates. */
val ScreenColorChange = Value("ScreenColorChange") val ScreenColorChange = Value("ScreenColorChange")
val ScreenCopy = Value("ScreenCopy") val ScreenCopy = Value("ScreenCopy")
val ScreenDepthChange = Value("ScreenDepthChange") val ScreenDepthChange = Value("ScreenDepthChange")
@ -42,11 +24,9 @@ object PacketType extends Enumeration {
val ScreenResolutionChange = Value("ScreenResolutionChange") val ScreenResolutionChange = Value("ScreenResolutionChange")
val ScreenSet = Value("ScreenSet") 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 KeyDown = Value("KeyDown")
val KeyUp = Value("KeyUp") val KeyUp = Value("KeyUp")
val Clipboard = Value("Clipboard") val Clipboard = Value("Clipboard")
val Analyze = Value("Analyze")
} }

View File

@ -32,13 +32,8 @@ class Cable(val parent: SpecialDelegator) extends SpecialDelegate {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def setBlockBoundsBasedOnState(world: IBlockAccess, x: Int, y: Int, z: Int) { override def setBlockBoundsBasedOnState(world: IBlockAccess, x: Int, y: Int, z: Int) {
val bounds = Cable.bounds(world, x, y, z) parent.setBlockBounds(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)
} }
} }

View File

@ -1,8 +1,7 @@
package li.cil.oc.common.block 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.common.tileentity
import li.cil.oc.{Config, OpenComputers}
import net.minecraft.client.renderer.texture.IconRegister import net.minecraft.client.renderer.texture.IconRegister
import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.Icon import net.minecraft.util.Icon
@ -10,7 +9,7 @@ import net.minecraft.world.IBlockAccess
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraftforge.common.ForgeDirection import net.minecraftforge.common.ForgeDirection
class Case(val parent: SimpleDelegator) extends SimpleDelegate { class Case(val parent: SimpleDelegator) extends Computer with SimpleDelegate {
val unlocalizedName = "Case" 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 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? // 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) = override def onBlockRemovedBy(world: World, x: Int, y: Int, z: Int, player: EntityPlayer) =
world.getBlockTileEntity(x, y, z) match { world.getBlockTileEntity(x, y, z) match {

View File

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

View File

@ -13,6 +13,7 @@ import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.{EnumCreatureType, Entity, EntityLivingBase} import net.minecraft.entity.{EnumCreatureType, Entity, EntityLivingBase}
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.tileentity.TileEntity import net.minecraft.tileentity.TileEntity
import net.minecraft.util.AxisAlignedBB
import net.minecraft.world.IBlockAccess import net.minecraft.world.IBlockAccess
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraftforge.common.ForgeDirection import net.minecraftforge.common.ForgeDirection
@ -284,6 +285,12 @@ class Delegator[Child <: Delegate](id: Int, name: String) extends Block(id, Mate
case _ => false 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) = override def setBlockBoundsBasedOnState(world: IBlockAccess, x: Int, y: Int, z: Int) =
subBlock(world, x, y, z) match { subBlock(world, x, y, z) match {
case Some(subBlock) => subBlock.setBlockBoundsBasedOnState(world, x, y, z) case Some(subBlock) => subBlock.setBlockBoundsBasedOnState(world, x, y, z)

View File

@ -1,14 +1,19 @@
package li.cil.oc.common.block package li.cil.oc.common.block
import li.cil.oc.common.tileentity 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" val unlocalizedName = "Robot"
override def createTileEntity(world: World) = Some(new tileentity.Robot)
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def hasTileEntity = true // override def setBlockBoundsBasedOnState(world: IBlockAccess, x: Int, y: Int, z: Int) {
// world.getBlockTileEntity(x, y, z) match {
override def createTileEntity(world: World) = Some(new tileentity.Robot) // case robot: tileentity.Robot => parent.setBlockBounds(robot.bounds)
// case _ => super.setBlockBoundsBasedOnState(world, x, y, z)
// }
// }
} }

View File

@ -2,10 +2,12 @@ package li.cil.oc.common.component
import li.cil.oc.api.network.Visibility import li.cil.oc.api.network.Visibility
import li.cil.oc.common.component import li.cil.oc.common.component
import li.cil.oc.common.tileentity
import li.cil.oc.common.tileentity.TileEntity import li.cil.oc.common.tileentity.TileEntity
import li.cil.oc.util.{Persistable, PackedColor, TextBuffer} import li.cil.oc.util.{Persistable, PackedColor, TextBuffer}
import li.cil.oc.{api, Config} import li.cil.oc.{api, Config}
import net.minecraft.nbt.NBTTagCompound import net.minecraft.nbt.NBTTagCompound
import scala.collection.convert.WrapAsScala._
class Buffer(val owner: Buffer.Environment) extends Persistable { class Buffer(val owner: Buffer.Environment) extends Persistable {
val buffer = new TextBuffer(maxResolution, maxDepth) val buffer = new TextBuffer(maxResolution, maxDepth)
@ -99,6 +101,22 @@ class Buffer(val owner: Buffer.Environment) extends Persistable {
} }
override def save(nbt: NBTTagCompound) = { 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 val screenNbt = new NBTTagCompound
buffer.save(screenNbt) buffer.save(screenNbt)
nbt.setCompoundTag(Config.namespace + "screen", screenNbt) nbt.setCompoundTag(Config.namespace + "screen", screenNbt)

View File

@ -5,7 +5,6 @@ import li.cil.oc.api.driver.Slot
import li.cil.oc.server.component import li.cil.oc.server.component
import li.cil.oc.server.driver.Registry import li.cil.oc.server.driver.Registry
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraftforge.common.ForgeDirection
class Case(isClient: Boolean) extends Computer { class Case(isClient: Boolean) extends Computer {
def this() = this(false) 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 getInvName = Config.namespace + "container.Case"
def getSizeInventory = 8 def getSizeInventory = 8
@ -34,8 +27,4 @@ class Case(isClient: Boolean) extends Computer {
case (6 | 7, Some(driver)) => driver.slot(item) == Slot.HardDiskDrive case (6 | 7, Some(driver)) => driver.slot(item) == Slot.HardDiskDrive
case _ => false // Invalid slot. case _ => false // Invalid slot.
} }
// ----------------------------------------------------------------------- //
def canConnectRedstone(side: ForgeDirection) = isOutputEnabled
} }

View File

@ -54,7 +54,7 @@ trait Inventory extends TileEntity with IInventory with Persistable {
def isUseableByPlayer(player: EntityPlayer) = def isUseableByPlayer(player: EntityPlayer) =
world.getBlockTileEntity(x, y, z) match { 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 case _ => false
} }

View File

@ -73,7 +73,7 @@ class Keyboard extends Environment with Rotatable {
private def isUseableByPlayer(p: EntityPlayer) = private def isUseableByPlayer(p: EntityPlayer) =
world.getBlockTileEntity(x, y, z) == this && 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 { object Keyboard extends IPlayerTracker {

View File

@ -47,36 +47,42 @@ class PowerDistributor extends Environment with Analyzable {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def changeBuffer(delta: Double): Boolean = { def changeBuffer(delta: Double): Boolean = {
if (delta != 0) { if (delta != 0) this.synchronized {
val oldBuffer = globalBuffer val oldBuffer = globalBuffer
globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize
if (globalBuffer != oldBuffer) { if (globalBuffer != oldBuffer) {
dirty = true dirty = true
if (delta < 0) { if (delta < 0) {
var remaining = -delta var remaining = -delta
for (connector <- buffers if connector.localBuffer > 0) { for (connector <- buffers) {
if (connector.localBuffer < remaining) { connector.synchronized(if (connector.localBuffer > 0) {
remaining -= connector.localBuffer connector.dirty = true
connector.localBuffer = 0 if (connector.localBuffer < remaining) {
} remaining -= connector.localBuffer
else { connector.localBuffer = 0
connector.changeBuffer(-remaining) }
return true else {
} connector.localBuffer -= remaining
return true
}
})
} }
} }
else if (delta > 0) { else if (delta > 0) {
var remaining = delta var remaining = delta
for (connector <- buffers if connector.localBuffer < connector.localBufferSize) { for (connector <- buffers) {
val space = connector.localBufferSize - connector.localBuffer connector.synchronized(if (connector.localBuffer < connector.localBufferSize) {
if (space < remaining) { connector.dirty = true
remaining -= space val space = connector.localBufferSize - connector.localBuffer
connector.localBuffer = connector.localBufferSize if (space < remaining) {
} remaining -= space
else { connector.localBuffer = connector.localBufferSize
connector.changeBuffer(remaining) }
return true else {
} connector.localBuffer += remaining
return true
}
})
} }
} }
} }
@ -106,10 +112,11 @@ class PowerDistributor extends Environment with Analyzable {
super.onConnect(node) super.onConnect(node)
if (node == this.node) { if (node == this.node) {
for (node <- node.network.nodes) node match { 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 buffers += connector
globalBuffer += connector.localBuffer globalBuffer += connector.localBuffer
globalBufferSize += connector.localBufferSize globalBufferSize += connector.localBufferSize
}
case _ => node.host match { case _ => node.host match {
case distributor: PowerDistributor => distributors += distributor case distributor: PowerDistributor => distributors += distributor
case _ => case _ =>
@ -118,11 +125,12 @@ class PowerDistributor extends Environment with Analyzable {
dirty = true dirty = true
} }
else node match { else node match {
case connector: Connector => case connector: Connector => this.synchronized {
buffers += connector buffers += connector
globalBuffer += connector.localBuffer globalBuffer += connector.localBuffer
globalBufferSize += connector.localBufferSize globalBufferSize += connector.localBufferSize
dirty = true dirty = true
}
case _ => node.host match { case _ => node.host match {
case distributor: PowerDistributor => distributors += distributor case distributor: PowerDistributor => distributors += distributor
case _ => case _ =>
@ -132,7 +140,7 @@ class PowerDistributor extends Environment with Analyzable {
override def onDisconnect(node: Node) { override def onDisconnect(node: Node) {
super.onDisconnect(node) super.onDisconnect(node)
if (node == this.node) { if (node == this.node) this.synchronized {
buffers.clear() buffers.clear()
distributors.clear() distributors.clear()
globalBuffer = 0 globalBuffer = 0
@ -140,11 +148,12 @@ class PowerDistributor extends Environment with Analyzable {
average = -1 average = -1
} }
else node match { else node match {
case connector: Connector => case connector: Connector => this.synchronized {
buffers -= connector buffers -= connector
globalBuffer -= connector.localBuffer globalBuffer -= connector.localBuffer
globalBufferSize -= connector.localBufferSize globalBufferSize -= connector.localBufferSize
dirty = true dirty = true
}
case _ => node.host match { case _ => node.host match {
case distributor: PowerDistributor => distributors -= distributor case distributor: PowerDistributor => distributors -= distributor
case _ => case _ =>
@ -163,7 +172,7 @@ class PowerDistributor extends Environment with Analyzable {
}) })
average = if (globalBufferSize > 0) globalBuffer / globalBufferSize else 0 average = if (globalBufferSize > 0) globalBuffer / globalBufferSize else 0
val shouldSend = (lastSentAverage - average).abs > 0.05 val shouldSend = (lastSentAverage - average).abs > 0.05
for (distributor <- distributors) { for (distributor <- distributors) distributor.synchronized {
distributor.dirty = false distributor.dirty = false
distributor.globalBuffer = sumBuffer distributor.globalBuffer = sumBuffer
distributor.globalBufferSize = sumBufferSize distributor.globalBufferSize = sumBufferSize

View File

@ -1,15 +1,88 @@
package li.cil.oc.common.tileentity 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.component
import li.cil.oc.server.driver.Registry
import net.minecraft.item.ItemStack 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.
}
} }

View File

@ -23,28 +23,28 @@ class Carriage(controller: AnyRef) extends ManagedComponent {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@LuaCallback(value = "move", direct = true) @LuaCallback("move")
def move(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { def move(context: Context, args: Arguments): Array[AnyRef] = {
direction = checkDirection(args) direction = checkDirection(args)
simulating = if (args.count > 1) args.checkBoolean(1) else false simulating = if (args.count > 1) args.checkBoolean(1) else false
shouldMove = true shouldMove = true
result(true) result(true)
} }
@LuaCallback(value = "simulate", direct = true) @LuaCallback("simulate")
def simulate(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { def simulate(context: Context, args: Arguments): Array[AnyRef] = {
direction = checkDirection(args) direction = checkDirection(args)
simulating = true simulating = true
shouldMove = true shouldMove = true
result(true) result(true)
} }
@LuaCallback(value = "getAnchored", direct = true) @LuaCallback("getAnchored")
def getAnchored(context: Context, args: Arguments): Array[AnyRef] = def getAnchored(context: Context, args: Arguments): Array[AnyRef] =
this.synchronized(result(anchored)) result(anchored)
@LuaCallback(value = "setAnchored", direct = true) @LuaCallback("setAnchored")
def setAnchored(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { def setAnchored(context: Context, args: Arguments): Array[AnyRef] = {
anchored = args.checkBoolean(0) anchored = args.checkBoolean(0)
result(anchored) result(anchored)
} }
@ -72,7 +72,7 @@ class Carriage(controller: AnyRef) extends ManagedComponent {
override def update() { override def update() {
super.update() super.update()
if (shouldMove) this.synchronized { if (shouldMove) {
shouldMove = false shouldMove = false
moving = true moving = true
try { try {
@ -104,7 +104,7 @@ class Carriage(controller: AnyRef) extends ManagedComponent {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def save(nbt: NBTTagCompound) { override def save(nbt: NBTTagCompound) = {
super.save(nbt) super.save(nbt)
nbt.setBoolean("moving", moving) nbt.setBoolean("moving", moving)
nbt.setBoolean("anchored", anchored) nbt.setBoolean("anchored", anchored)

View File

@ -97,6 +97,10 @@ class Computer(val owner: tileentity.Computer) extends Persistable with Runnable
true true
}) })
def pause() = if (state.synchronized(isRunning && !isStopping)) {
this.synchronized(if (!isStopping) state.push(Computer.State.Paused))
}
def stop() = state.synchronized(state.top match { def stop() = state.synchronized(state.top match {
case Computer.State.Stopped | Computer.State.Stopping => false case Computer.State.Stopped | Computer.State.Stopping => false
case _ => state.push(Computer.State.Stopping); true 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 isRunning = state.synchronized(state.top != Computer.State.Stopped)
def isStopping = state.synchronized(state.top == Computer.State.Stopping)
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def isUser(player: String) = !Config.canComputersBeOwned || 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.getTop == 2)
assert(lua.isThread(1)) assert(lua.isThread(1))
assert(lua.isFunction(2)) 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 // We switch into running state, since we'll behave as though the call
// were performed from our executor thread. // were performed from our executor thread.
switchTo(Computer.State.Running) switchTo(Computer.State.Running)
@ -931,18 +942,8 @@ class Computer(val owner: tileentity.Computer) extends Persistable with Runnable
lua.setGlobal("component") 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() 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.load(classOf[Computer].getResourceAsStream(Config.scriptPath + "kernel.lua"), "=kernel", "t")
lua.newThread() // Left as the first value on the stack. 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 future = None
val enterState = state.synchronized { 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. // See if the game appears to be paused, in which case we also pause.
if (System.currentTimeMillis - lastUpdate > 100) { if (System.currentTimeMillis - lastUpdate > 100) {
state.push(Computer.State.Paused) state.push(Computer.State.Paused)
return return
} }
if (state.top == Computer.State.Stopping)
return
switchTo(Computer.State.Running) switchTo(Computer.State.Running)
} }

View File

@ -19,11 +19,10 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@LuaCallback(value = "getLabel", direct = true) @LuaCallback(value = "getLabel", direct = true)
def getLabel(context: Context, args: Arguments): Array[AnyRef] = def getLabel(context: Context, args: Arguments): Array[AnyRef] = result(label.getLabel)
this.synchronized(result(label.getLabel))
@LuaCallback(value = "setLabel", direct = true) @LuaCallback("setLabel")
def setLabel(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { def setLabel(context: Context, args: Arguments): Array[AnyRef] = {
if (label == null) throw new Exception("filesystem does not support labeling") if (label == null) throw new Exception("filesystem does not support labeling")
if (args.checkAny(0) == null) label.setLabel(null) if (args.checkAny(0) == null) label.setLabel(null)
else label.setLabel(args.checkString(0)) 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) @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) result(fileSystem.isReadOnly)
} }
@LuaCallback(value = "spaceTotal", direct = true) @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 val space = fileSystem.spaceTotal
if (space < 0) if (space < 0)
Array("unlimited") Array("unlimited")
@ -45,59 +44,59 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma
} }
@LuaCallback(value = "spaceUsed", direct = true) @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) result(fileSystem.spaceUsed)
} }
@LuaCallback(value = "exists", direct = true) @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)))) result(fileSystem.exists(clean(args.checkString(0))))
} }
@LuaCallback(value = "size", direct = true) @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)))) result(fileSystem.size(clean(args.checkString(0))))
} }
@LuaCallback(value = "isDirectory", direct = true) @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)))) result(fileSystem.isDirectory(clean(args.checkString(0))))
} }
@LuaCallback(value = "lastModified", direct = true) @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)))) result(fileSystem.lastModified(clean(args.checkString(0))))
} }
@LuaCallback("list") @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 { Option(fileSystem.list(clean(args.checkString(0)))) match {
case Some(list) => Array(list) case Some(list) => Array(list)
case _ => null case _ => null
} }
} }
@LuaCallback("makeDirectory") @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) || def recurse(path: String): Boolean = !fileSystem.exists(path) && (fileSystem.makeDirectory(path) ||
(recurse(path.split("/").dropRight(1).mkString("/")) && fileSystem.makeDirectory(path))) (recurse(path.split("/").dropRight(1).mkString("/")) && fileSystem.makeDirectory(path)))
result(recurse(clean(args.checkString(0)))) result(recurse(clean(args.checkString(0))))
} }
@LuaCallback("remove") @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) || def recurse(parent: String): Boolean = (!fileSystem.isDirectory(parent) ||
fileSystem.list(parent).forall(child => recurse(parent + "/" + child))) && fileSystem.delete(parent) fileSystem.list(parent).forall(child => recurse(parent + "/" + child))) && fileSystem.delete(parent)
result(recurse(clean(args.checkString(0)))) result(recurse(clean(args.checkString(0))))
} }
@LuaCallback("rename") @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)))) result(fileSystem.rename(clean(args.checkString(0)), clean(args.checkString(1))))
} }
@LuaCallback("close") @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) val handle = args.checkInteger(0)
Option(fileSystem.getHandle(handle)) match { Option(fileSystem.getHandle(handle)) match {
case Some(file) => case Some(file) =>
@ -111,8 +110,8 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma
} }
@LuaCallback("open") @LuaCallback("open")
def open(context: Context, args: Arguments): Array[AnyRef] = fileSystem.synchronized { def open(context: Context, args: Arguments): Array[AnyRef] = {
if (owners.get(context.address).fold(false)(_.size >= Config.maxHandles)) if (owners.get(context.address).fold(false)(_.size >= Config.maxHandles))
throw new IOException("too many open handles") throw new IOException("too many open handles")
else { else {
val path = args.checkString(0) val path = args.checkString(0)
@ -126,7 +125,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma
} }
@LuaCallback("read") @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 handle = args.checkInteger(0)
val n = args.checkInteger(1) val n = args.checkInteger(1)
checkOwner(context.address, handle) checkOwner(context.address, handle)
@ -157,7 +156,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma
} }
@LuaCallback("seek") @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 handle = args.checkInteger(0)
val whence = args.checkString(1) val whence = args.checkString(1)
val offset = args.checkInteger(2) val offset = args.checkInteger(2)
@ -176,7 +175,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma
} }
@LuaCallback("write") @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 handle = args.checkInteger(0)
val value = args.checkByteArray(1) val value = args.checkByteArray(1)
if (!node.changeBuffer(-Config.hddWriteCost * value.length)) { 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 { message.data match {
case Array() if message.name == "computer.stopped" || message.name == "computer.started" => case Array() if message.name == "computer.stopped" || message.name == "computer.started" =>
owners.get(message.source.address) match { owners.get(message.source.address) match {
case Some(set) => fileSystem.synchronized { case Some(set) => {
set.foreach(handle => Option(fileSystem.getHandle(handle)) match { set.foreach(handle => Option(fileSystem.getHandle(handle)) match {
case Some(file) => file.close() case Some(file) => file.close()
case _ => // Invalid handle... huh. 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) { override def onDisconnect(node: Node) {
super.onDisconnect(node) super.onDisconnect(node)
if (node == this.node) fileSystem.synchronized { if (node == this.node) {
fileSystem.close() fileSystem.close()
} }
else if (owners.contains(node.address)) fileSystem.synchronized { else if (owners.contains(node.address)) {
for (handle <- owners(node.address)) { for (handle <- owners(node.address)) {
Option(fileSystem.getHandle(handle)) match { Option(fileSystem.getHandle(handle)) match {
case Some(file) => file.close() case Some(file) => file.close()

View File

@ -21,7 +21,15 @@ abstract class GraphicsCard extends ManagedComponent {
private var screenInstance: Option[Buffer] = None 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) { if (screenInstance.isEmpty && screenAddress.isDefined) {
Option(node.network.node(screenAddress.get)) match { Option(node.network.node(screenAddress.get)) match {
case Some(node: Node) if node.host.isInstanceOf[Buffer.Environment] => case Some(node: Node) if node.host.isInstanceOf[Buffer.Environment] =>
@ -35,22 +43,16 @@ abstract class GraphicsCard extends ManagedComponent {
screenAddress = None screenAddress = None
} }
} }
screenInstance match {
case Some(screen) => f(screen)
case _ => Array(Unit, "no screen")
}
} }
// ----------------------------------------------------------------------- //
@LuaCallback("bind") @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) val address = args.checkString(0)
node.network.node(address) match { node.network.node(address) match {
case null => Array(Unit, "invalid address") 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) screenAddress = Option(address)
screenInstance = None screenInstance = Some(node.host.asInstanceOf[Buffer.Environment].instance)
screen(s => { screen(s => {
val (gmw, gmh) = maxResolution val (gmw, gmh) = maxResolution
val (smw, smh) = s.maxResolution val (smw, smh) = s.maxResolution
@ -60,6 +62,7 @@ abstract class GraphicsCard extends ManagedComponent {
s.background = 0x000000 s.background = 0x000000
result(true) result(true)
}) })
}
case _ => Array(Unit, "not a screen") case _ => Array(Unit, "not a screen")
} }
} }
@ -220,6 +223,18 @@ abstract class GraphicsCard extends ManagedComponent {
object GraphicsCard { 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 { class Tier1 extends GraphicsCard {
val maxDepth = Config.screenDepthsByTier(0) val maxDepth = Config.screenDepthsByTier(0)
val maxResolution = Config.screenResolutionsByTier(0) val maxResolution = Config.screenResolutionsByTier(0)

View File

@ -16,13 +16,13 @@ class NetworkCard extends ManagedComponent {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@LuaCallback("open") @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)) val port = checkPort(args.checkInteger(0))
result(openPorts.add(port)) result(openPorts.add(port))
} }
@LuaCallback("close") @LuaCallback("close")
def close(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { def close(context: Context, args: Arguments): Array[AnyRef] = {
if (args.count == 0) { if (args.count == 0) {
openPorts.clear() openPorts.clear()
result(true) result(true)
@ -34,7 +34,7 @@ class NetworkCard extends ManagedComponent {
} }
@LuaCallback(value = "isOpen", direct = true) @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)) val port = checkPort(args.checkInteger(0))
result(openPorts.contains(port)) 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) super.onMessage(message)
if ((message.name == "computer.stopped" || message.name == "computer.started") && node.isNeighborOf(message.source)) if ((message.name == "computer.stopped" || message.name == "computer.started") && node.isNeighborOf(message.source))
openPorts.clear() openPorts.clear()

View File

@ -22,15 +22,15 @@ class WirelessNetworkCard(val owner: TileEntity) extends NetworkCard {
@LuaCallback(value = "getStrength", direct = true) @LuaCallback(value = "getStrength", direct = true)
def getStrength(context: Context, args: Arguments): Array[AnyRef] = result(strength) def getStrength(context: Context, args: Arguments): Array[AnyRef] = result(strength)
@LuaCallback(value = "setStrength", direct = true) @LuaCallback("setStrength")
def setStrength(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { def setStrength(context: Context, args: Arguments): Array[AnyRef] = {
strength = args.checkDouble(0) max 0 min Config.maxWirelessRange strength = args.checkDouble(0) max 0 min Config.maxWirelessRange
result(strength) result(strength)
} }
override def isWireless(context: Context, args: Arguments): Array[AnyRef] = result(true) 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 address = args.checkString(0)
val port = checkPort(args.checkInteger(1)) val port = checkPort(args.checkInteger(1))
if (strength > 0) { if (strength > 0) {
@ -44,7 +44,7 @@ class WirelessNetworkCard(val owner: TileEntity) extends NetworkCard {
super.send(context, args) 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)) val port = checkPort(args.checkInteger(0))
if (strength > 0) { if (strength > 0) {
checkPower() checkPower()

View File

@ -26,21 +26,24 @@ trait Connector extends Node with network.Connector with Persistable {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def changeBuffer(delta: Double) = if (delta != 0) { def changeBuffer(delta: Double) = if (delta != 0) {
val oldBuffer = localBuffer val remaining = this.synchronized {
localBuffer = localBuffer + delta val oldBuffer = localBuffer
val ok = if (localBuffer < 0) { localBuffer = localBuffer + delta
val remaining = localBuffer val remaining = if (localBuffer < 0) {
localBuffer = 0 val remaining = localBuffer
distributor.fold(false)(_.changeBuffer(remaining)) localBuffer = 0
remaining
}
else if (localBuffer > localBufferSize) {
val remaining = localBuffer - localBufferSize
localBuffer = localBufferSize
remaining
}
else 0
dirty ||= (localBuffer != oldBuffer)
remaining
} }
else if (localBuffer > localBufferSize) { distributor.fold(remaining == 0)(_.changeBuffer(remaining)) || Config.ignorePower
val remaining = localBuffer - localBufferSize
localBuffer = localBufferSize
distributor.fold(false)(_.changeBuffer(remaining))
}
else true
dirty ||= (localBuffer != oldBuffer)
ok || Config.ignorePower
} else true } else true
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@ -59,7 +62,7 @@ trait Connector extends Node with network.Connector with Persistable {
super.onDisconnect(node) 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])) distributor = reachableNodes.find(_.host.isInstanceOf[PowerDistributor]).fold(None: Option[PowerDistributor])(n => Some(n.host.asInstanceOf[PowerDistributor]))
} }

View File

@ -12,7 +12,7 @@ import scala.collection.JavaConverters._
import scala.collection.mutable import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer 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) = { def this(node: MutableNode) = {
this() this()
addNew(node) 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( if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot connect a node to itself.") "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) else add(oldNodeB, nodeA)
} }
def disconnect(nodeA: MutableNode, nodeB: MutableNode) = this.synchronized { def disconnect(nodeA: MutableNode, nodeB: MutableNode) = {
if (nodeA == nodeB) throw new IllegalArgumentException( if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot disconnect a node from itself.") "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 { data.remove(node.address) match {
case Some(entry) => { case Some(entry) => {
node.network = null 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 { data.get(address) match {
case Some(node) => node.data case Some(node) => node.data
case _ => null 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] = { def nodes(reference: ImmutableNode): Iterable[ImmutableNode] = {
val referenceNeighbors = neighbors(reference).toSet 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)))) (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 { data.get(node.address) match {
case Some(n) => case Some(n) =>
assert(n.data == node) 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) if (source.network != wrapper)
throw new IllegalArgumentException("Source node must be in this network.") throw new IllegalArgumentException("Source node must be in this network.")
data.get(target) match { 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) if (source.network != wrapper)
throw new IllegalArgumentException("Source node must be in this network.") throw new IllegalArgumentException("Source node must be in this network.")
send(source, neighbors(source).filter(_.reachability != Visibility.None), name, args: _*) 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) if (source.network != wrapper)
throw new IllegalArgumentException("Source node must be in this network.") throw new IllegalArgumentException("Source node must be in this network.")
send(source, nodes(source), name, args: _*) send(source, nodes(source), name, args: _*)
@ -386,8 +386,7 @@ object Network extends api.detail.NetworkAPI {
def disconnect(nodeA: ImmutableNode, nodeB: ImmutableNode) = def disconnect(nodeA: ImmutableNode, nodeB: ImmutableNode) =
network.disconnect(nodeA.asInstanceOf[MutableNode], nodeB.asInstanceOf[MutableNode]) network.disconnect(nodeA.asInstanceOf[MutableNode], nodeB.asInstanceOf[MutableNode])
def remove(node: ImmutableNode) = def remove(node: ImmutableNode) = network.remove(node.asInstanceOf[MutableNode])
network.remove(node.asInstanceOf[MutableNode])
def node(address: String) = network.node(address) def node(address: String) = network.node(address)

View File

@ -48,8 +48,7 @@ trait Node extends api.network.Node with Persistable {
def sendToReachable(name: String, data: AnyRef*) = def sendToReachable(name: String, data: AnyRef*) =
if (network != null) network.sendToReachable(this, name, data: _*) if (network != null) network.sendToReachable(this, name, data: _*)
private def isInSameNetwork(other: ImmutableNode) = private def isInSameNetwork(other: ImmutableNode) = network != null && network == other.network
network != null && network == other.network
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

View File

@ -1,13 +1,30 @@
package li.cil.oc.util package li.cil.oc.util
import li.cil.oc.OpenComputers
import net.minecraft.client.renderer.OpenGlHelper import net.minecraft.client.renderer.OpenGlHelper
import org.lwjgl.opengl._ import org.lwjgl.opengl._
import org.lwjgl.util.glu.GLU
object RenderState { object RenderState {
val arb = GLContext.getCapabilities.GL_ARB_multitexture && !GLContext.getCapabilities.OpenGL13 val arb = GLContext.getCapabilities.GL_ARB_multitexture && !GLContext.getCapabilities.OpenGL13
private val canUseBlendColor = GLContext.getCapabilities.OpenGL14 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() { def disableLighting() {
GL11.glDisable(GL11.GL_LIGHTING) GL11.glDisable(GL11.GL_LIGHTING)
if (arb) { if (arb) {