mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-09 07:15:11 -04:00
some more prep work for writable file systems based in save dir; reworked computer code structure a bit, moving more semi-internal stuff into the environment trait
This commit is contained in:
parent
2c14ec95b8
commit
0927f076ce
@ -136,6 +136,8 @@ function sandbox.load(code, source, env)
|
||||
return load(code, source, "t", env or sandbox)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--[[ Install wrappers for coroutine management that reserves the first value
|
||||
returned by yields for internal stuff. Used for sleeping and message
|
||||
calls (sendToNode and its ilk) that happen synchronized (Server thread).
|
||||
@ -148,7 +150,7 @@ local function checkDeadline()
|
||||
end
|
||||
end
|
||||
|
||||
local function main()
|
||||
local function main(args)
|
||||
local function init()
|
||||
sandbox.driver.fs.mount(os.romAddress(), "/rom")
|
||||
local result, reason = sandbox.loadfile("/rom/init.lua")
|
||||
@ -158,7 +160,6 @@ local function main()
|
||||
return coroutine.create(result)
|
||||
end
|
||||
local co = init()
|
||||
local args = {}
|
||||
while true do
|
||||
deadline = os.realTime() + timeout -- timeout global is set by host
|
||||
if not debug.gethook(co) then
|
||||
@ -198,6 +199,16 @@ function sandbox.coroutine.yield(...)
|
||||
return coroutine.yield(nil, ...)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function sandbox.os.shutdown()
|
||||
coroutine.yield(false)
|
||||
end
|
||||
|
||||
function sandbox.os.reboot()
|
||||
coroutine.yield(true)
|
||||
end
|
||||
|
||||
function sandbox.os.signal(name, timeout)
|
||||
local waitUntil = os.clock() + (type(timeout) == "number" and timeout or math.huge)
|
||||
while os.clock() < waitUntil do
|
||||
@ -208,13 +219,7 @@ function sandbox.os.signal(name, timeout)
|
||||
end
|
||||
end
|
||||
|
||||
function sandbox.os.shutdown()
|
||||
coroutine.yield(false)
|
||||
end
|
||||
|
||||
function sandbox.os.reboot()
|
||||
coroutine.yield(true)
|
||||
end
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
sandbox.driver = {}
|
||||
|
||||
@ -239,9 +244,9 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
-- Yield once to allow initializing up to here to get a memory baseline.
|
||||
coroutine.yield()
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- JNLua converts the coroutine to a string immediately, so we can't get the
|
||||
-- traceback later. Because of that we have to do the error handling here.
|
||||
return pcall(main)
|
||||
-- Also, yield once to allow initializing up to here to get a memory baseline.
|
||||
return pcall(main, {coroutine.yield()})
|
||||
|
@ -3,6 +3,7 @@ dofile("/rom/api/component.lua")
|
||||
dofile("/rom/api/term.lua")
|
||||
dofile("/rom/sh.lua")
|
||||
|
||||
event.fire(...)
|
||||
while true do
|
||||
event.fire(os.signal())
|
||||
end
|
||||
|
@ -4,6 +4,7 @@ import java.io.File
|
||||
|
||||
object Config {
|
||||
val resourceDomain = "opencomputers"
|
||||
val savePath = "opencomputers/"
|
||||
val scriptPath = "/assets/" + resourceDomain + "/lua/"
|
||||
val driverPath = "/assets/" + resourceDomain + "/lua/drivers/"
|
||||
|
||||
|
@ -176,6 +176,24 @@ object FileSystem extends FileSystemAPI {
|
||||
def fromClass(clazz: Class[_], domain: String, root: String = "") =
|
||||
instance.fold(None: Option[FileSystem])(_.fromClass(clazz, domain, root))
|
||||
|
||||
/**
|
||||
* Creates a new *writable* file system in the save folder.
|
||||
* <p/>
|
||||
* This will create a folder, if necessary, and create a writable virtual
|
||||
* file system based in that folder. The actual path is based in a sub-
|
||||
* folder of the save folder. The actual path is e.g. built like this:
|
||||
* `"saves/" + WORLD_NAME + "/opencomputers/" + root`. Where the first
|
||||
* part may differ, in particular for servers. But you get the idea.
|
||||
* <p/>
|
||||
* Usually the name will be the name of the node used to represent the
|
||||
* file system.
|
||||
*
|
||||
* @param root the name of the file system.
|
||||
* @return
|
||||
*/
|
||||
def fromSaveDir(root: String) =
|
||||
instance.fold(None: Option[FileSystem])(_.fromSaveDir(root))
|
||||
|
||||
/**
|
||||
* Creates a network node that makes the specified file system available via
|
||||
* the common file system driver.
|
||||
|
@ -7,5 +7,7 @@ import li.cil.oc.api.network.Node
|
||||
trait FileSystemAPI {
|
||||
def fromClass(clazz: Class[_], domain: String, root: String): Option[FileSystem]
|
||||
|
||||
def fromSaveDir(root: String): Option[FileSystem]
|
||||
|
||||
def asNode(fs: FileSystem): Option[Node]
|
||||
}
|
@ -68,7 +68,8 @@ class Computer(val parent: Delegator) extends Delegate {
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def breakBlock(world: World, x: Int, y: Int, z: Int, blockId: Int, metadata: Int) = {
|
||||
world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Computer].turnOff()
|
||||
if (!world.isRemote)
|
||||
world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Computer].turnOff()
|
||||
super.breakBlock(world, x, y, z, blockId, metadata)
|
||||
}
|
||||
|
||||
@ -76,7 +77,8 @@ class Computer(val parent: Delegator) extends Delegate {
|
||||
side: ForgeDirection, hitX: Float, hitY: Float, hitZ: Float) = {
|
||||
if (!player.isSneaking) {
|
||||
// Start the computer if it isn't already running and open the GUI.
|
||||
world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Computer].turnOn()
|
||||
if (!world.isRemote)
|
||||
world.getBlockTileEntity(x, y, z).asInstanceOf[tileentity.Computer].turnOn()
|
||||
player.openGui(OpenComputers, GuiType.Computer.id, world, x, y, z)
|
||||
true
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ trait ComponentInventory extends IInventory with Node {
|
||||
|
||||
protected val itemComponents = Array.fill[Option[Node]](inventorySize)(None)
|
||||
|
||||
protected val computer: Option[component.Computer]
|
||||
protected val computer: component.Computer
|
||||
|
||||
def world: World
|
||||
|
||||
@ -134,18 +134,20 @@ trait ComponentInventory extends IInventory with Node {
|
||||
if (item != null && item.stackSize > getInventoryStackLimit)
|
||||
item.stackSize = getInventoryStackLimit
|
||||
|
||||
if (!world.isRemote) Registry.driverFor(inventory(slot)) match {
|
||||
case None => // No driver.
|
||||
case Some(driver) =>
|
||||
driver.node(inventory(slot)) match {
|
||||
case None => // No node.
|
||||
case Some(node) =>
|
||||
itemComponents(slot) = Some(node)
|
||||
network.foreach(_.connect(this, node))
|
||||
}
|
||||
}
|
||||
if (!world.isRemote) {
|
||||
Registry.driverFor(inventory(slot)) match {
|
||||
case None => // No driver.
|
||||
case Some(driver) =>
|
||||
driver.node(inventory(slot)) match {
|
||||
case None => // No node.
|
||||
case Some(node) =>
|
||||
itemComponents(slot) = Some(node)
|
||||
network.foreach(_.connect(this, node))
|
||||
}
|
||||
}
|
||||
|
||||
computer.foreach(_.recomputeMemory())
|
||||
computer.recomputeMemory()
|
||||
}
|
||||
}
|
||||
|
||||
def isItemValidForSlot(slot: Int, item: ItemStack) = (slot, Registry.driverFor(item)) match {
|
||||
|
@ -13,56 +13,19 @@ import net.minecraftforge.common.ForgeDirection
|
||||
class Computer(isClient: Boolean) extends Rotatable with component.Computer.Environment with ComponentInventory with RedstoneEnabled {
|
||||
def this() = this(false)
|
||||
|
||||
protected val computer = if (isClient) None else Some(new component.Computer(this))
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private val hasChanged = new AtomicBoolean(true)
|
||||
private val hasChanged = new AtomicBoolean(true) // For `markChanged`.
|
||||
|
||||
private var isRunning = false
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// NetworkNode
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def receive(message: Message) = {
|
||||
super.receive(message)
|
||||
message.data match {
|
||||
// The isRunning check is here to avoid component_* signals being
|
||||
// generated while loading a chunk.
|
||||
case Array() if message.name == "network.connect" && isRunning =>
|
||||
computer.foreach(_.signal("component_added", message.source.address.get)); None
|
||||
case Array() if message.name == "network.disconnect" && isRunning =>
|
||||
computer.foreach(_.signal("component_removed", message.source.address.get)); None
|
||||
case Array(oldAddress: Integer) if message.name == "network.reconnect" && isRunning =>
|
||||
computer.foreach(_.signal("component_changed", message.source.address.get, oldAddress)); None
|
||||
case Array(name: String, args@_*) if message.name == "computer.signal" =>
|
||||
computer.foreach(_.signal(name, List(message.source.address.get) ++ args: _*)); None
|
||||
case Array() if message.name == "computer.start" =>
|
||||
Some(Array(turnOn().asInstanceOf[Any]))
|
||||
case Array() if message.name == "computer.stop" =>
|
||||
Some(Array(turnOff().asInstanceOf[Any]))
|
||||
case Array() if message.name == "computer.running" =>
|
||||
Some(Array(isOn.asInstanceOf[Any]))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
override protected def onConnect() {
|
||||
super.onConnect()
|
||||
network.foreach(_.connect(this, computer.get.rom))
|
||||
}
|
||||
|
||||
override protected def onDisconnect() {
|
||||
super.onDisconnect()
|
||||
computer.foreach(c => c.rom.network.foreach(_.remove(c.rom)))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// General
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
def turnOn() = computer.foreach(_.start())
|
||||
def turnOn() = computer.start()
|
||||
|
||||
def turnOff() = computer.foreach(_.stop())
|
||||
def turnOff() = computer.stop()
|
||||
|
||||
def isOn = isRunning
|
||||
|
||||
@ -75,7 +38,6 @@ class Computer(isClient: Boolean) extends Rotatable with component.Computer.Envi
|
||||
override def readFromNBT(nbt: NBTTagCompound) = {
|
||||
super.readFromNBT(nbt)
|
||||
load(nbt.getCompoundTag("data"))
|
||||
computer.foreach(_.load(nbt.getCompoundTag("computer")))
|
||||
}
|
||||
|
||||
override def writeToNBT(nbt: NBTTagCompound) = {
|
||||
@ -84,25 +46,15 @@ class Computer(isClient: Boolean) extends Rotatable with component.Computer.Envi
|
||||
val dataNbt = new NBTTagCompound
|
||||
save(dataNbt)
|
||||
nbt.setCompoundTag("data", dataNbt)
|
||||
|
||||
val computerNbt = new NBTTagCompound
|
||||
computer.foreach(_.save(computerNbt))
|
||||
nbt.setCompoundTag("computer", computerNbt)
|
||||
}
|
||||
|
||||
override def updateEntity() = if (!worldObj.isRemote) {
|
||||
computer.get.update()
|
||||
if (hasChanged.get) {
|
||||
computer.update()
|
||||
if (hasChanged.get)
|
||||
worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this)
|
||||
}
|
||||
if (isRunning != computer.get.isRunning) {
|
||||
isRunning = computer.get.isRunning
|
||||
if (isRunning)
|
||||
network.foreach(_.sendToVisible(this, "computer.started"))
|
||||
else
|
||||
network.foreach(_.sendToVisible(this, "computer.stopped"))
|
||||
ServerPacketSender.sendComputerState(this, isRunning)
|
||||
}
|
||||
if (isRunning != computer.isRunning)
|
||||
ServerPacketSender.sendComputerState(this, computer.isRunning)
|
||||
isRunning = computer.isRunning
|
||||
}
|
||||
|
||||
override def validate() = {
|
||||
@ -111,6 +63,24 @@ class Computer(isClient: Boolean) extends Rotatable with component.Computer.Envi
|
||||
ClientPacketSender.sendComputerStateRequest(this)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// Computer.Environment
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override protected val computer = if (isClient) null else new component.Computer(this)
|
||||
|
||||
def world = worldObj
|
||||
|
||||
def markAsChanged() = hasChanged.set(true)
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// IInventory
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def isUseableByPlayer(player: EntityPlayer) =
|
||||
worldObj.getBlockTileEntity(xCoord, yCoord, zCoord) == this &&
|
||||
player.getDistanceSq(xCoord + 0.5, yCoord + 0.5, zCoord + 0.5) < 64
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// RedstoneEnabled
|
||||
// ----------------------------------------------------------------------- //
|
||||
@ -119,16 +89,4 @@ class Computer(isClient: Boolean) extends Rotatable with component.Computer.Envi
|
||||
xCoord + side.offsetX, yCoord + side.offsetY, zCoord + side.offsetZ, side.getOpposite.ordinal)
|
||||
|
||||
// TODO output
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// Interfaces and updating
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def isUseableByPlayer(player: EntityPlayer) =
|
||||
worldObj.getBlockTileEntity(xCoord, yCoord, zCoord) == this &&
|
||||
player.getDistanceSq(xCoord + 0.5, yCoord + 0.5, zCoord + 0.5) < 64
|
||||
|
||||
override def world = worldObj
|
||||
|
||||
override def markAsChanged() = hasChanged.set(true)
|
||||
}
|
@ -7,7 +7,7 @@ import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.logging.Level
|
||||
import li.cil.oc.api
|
||||
import li.cil.oc.api.Persistable
|
||||
import li.cil.oc.api.network.{Visibility, Node}
|
||||
import li.cil.oc.api.network.{Message, Visibility, Node}
|
||||
import li.cil.oc.common.tileentity
|
||||
import li.cil.oc.server.driver
|
||||
import li.cil.oc.util.ExtendedLuaState.extendLuaState
|
||||
@ -31,118 +31,60 @@ import scala.io.Source
|
||||
* <ul>
|
||||
* <li>Creating a new Lua state when started from a previously stopped state.</li>
|
||||
* <li>Updating the Lua state in a parallel thread so as not to block the game.</li>
|
||||
* <li>Synchronizing calls from the computer thread to the network.</li>
|
||||
* <li>Synchronizing calls from the computer thread to other game components.</li>
|
||||
* <li>Saving the internal state of the computer across chunk saves/loads.</li>
|
||||
* <li>Closing the Lua state when stopping a previously running computer.</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* See `Driver` to read more about component drivers and how they interact
|
||||
* with computers - and through them the components they interface.
|
||||
* Computers are relatively useless without drivers. Drivers are a combination
|
||||
* of Lua and Java/Scala code that allows the computer to interact with the
|
||||
* game world, or more specifically: with other components, by sending messages
|
||||
* across the component network the computer is connected to (see `Network`).
|
||||
* <p/>
|
||||
* Host code (Java/Scala) cannot directly call Lua code. It can only queue
|
||||
* signals (events, messages, packets, whatever you want to call it) which will
|
||||
* be passed to the Lua state one by one and processed there (see `signal`).
|
||||
* <p/>
|
||||
*
|
||||
*/
|
||||
class Computer(val owner: Computer.Environment) extends Persistable with Runnable {
|
||||
// ----------------------------------------------------------------------- //
|
||||
// General
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
/**
|
||||
* The current execution state of the computer. This is used to track how to
|
||||
* resume the computers main thread, if at all, and whether to accept new
|
||||
* signals or not.
|
||||
*/
|
||||
private var state = Computer.State.Stopped
|
||||
|
||||
/** The internal Lua state. Only set while the computer is running. */
|
||||
private var lua: LuaState = null
|
||||
private val stateMonitor = new Object() // To synchronize access to `state`.
|
||||
|
||||
/** The filesystem node holding the read-only memory of this computer. */
|
||||
val rom = api.FileSystem.asNode(api.FileSystem.fromClass(OpenComputers.getClass, Config.resourceDomain, "lua/rom").get).get
|
||||
|
||||
/**
|
||||
* The base memory consumption of the kernel. Used to permit a fixed base
|
||||
* memory for userland even if the amount of memory the kernel uses changes
|
||||
* over time (i.e. with future releases of the mod). This is set when
|
||||
* starting up the computer.
|
||||
*/
|
||||
private var kernelMemory = 0
|
||||
|
||||
/**
|
||||
* The queue of signals the Lua state should process. Signals are queued from
|
||||
* the Java side and processed one by one in the Lua VM. They are the only
|
||||
* means to communicate actively with the computer (passively only message
|
||||
* handlers can interact with the computer by returning some result).
|
||||
* <p/>
|
||||
* The queue is intentionally pretty big, because we have to enqueue one
|
||||
* signal for for each component in the network when the computer starts up.
|
||||
*/
|
||||
private val signals = new LinkedBlockingQueue[Computer.Signal](256)
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
/**
|
||||
* The time (world time) when the computer was started. This is used for our
|
||||
* custom implementation of os.clock(), which returns the amount of the time
|
||||
* the computer has been running.
|
||||
*/
|
||||
private var timeStarted = 0L
|
||||
|
||||
/**
|
||||
* The last time (system time) the update function was called by the server
|
||||
* thread. We use this to detect whether the game was paused, to also pause
|
||||
* the executor thread for our Lua state.
|
||||
*/
|
||||
private var lastUpdate = 0L
|
||||
|
||||
/**
|
||||
* The current world time. This is used for our custom implementation of
|
||||
* os.time(). This is updated by the server thread and read by the computer
|
||||
* thread, to avoid computer threads directly accessing the world state.
|
||||
*/
|
||||
private var worldTime = 0L
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
/**
|
||||
* This is used to keep track of the current executor of the Lua state, for
|
||||
* example to wait for the computer to finish running a task.
|
||||
*/
|
||||
private var future: Option[Future[_]] = None
|
||||
|
||||
/**
|
||||
* Timestamp until which to sleep, i.e. when we hit this time we will create
|
||||
* a future to run the computer. Until then we have nothing to do.
|
||||
*/
|
||||
private var sleepUntil = Long.MaxValue
|
||||
private var lua: LuaState = null
|
||||
|
||||
/** This is used to synchronize access to the state field. */
|
||||
private val stateMonitor = new Object()
|
||||
private var kernelMemory = 0
|
||||
|
||||
private val signals = new LinkedBlockingQueue[Computer.Signal](256)
|
||||
|
||||
private val rom = api.FileSystem.
|
||||
fromClass(OpenComputers.getClass, Config.resourceDomain, "lua/rom").
|
||||
flatMap(api.FileSystem.asNode)
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// IComputerContext
|
||||
|
||||
private var timeStarted = 0L // Game-world time for os.clock().
|
||||
|
||||
private var worldTime = 0L // Game-world time for os.time().
|
||||
|
||||
private var lastUpdate = 0L // Real-world time for pause detection.
|
||||
|
||||
private var sleepUntil = Long.MaxValue // Real-world time.
|
||||
|
||||
private var wasRunning = false // To signal stops synchronously.
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
def signal(name: String, args: Any*) = stateMonitor.synchronized(state match {
|
||||
case Computer.State.Stopped | Computer.State.Stopping => false
|
||||
case _ => signals.offer(new Computer.Signal(name, args.map {
|
||||
case null | Unit => Unit
|
||||
case arg: Boolean => arg
|
||||
case arg: Byte => arg.toDouble
|
||||
case arg: Char => arg.toDouble
|
||||
case arg: Short => arg.toDouble
|
||||
case arg: Int => arg.toDouble
|
||||
case arg: Long => arg.toDouble
|
||||
case arg: Float => arg.toDouble
|
||||
case arg: Double => arg
|
||||
case arg: String => arg
|
||||
case _ => throw new IllegalArgumentException()
|
||||
}.toArray))
|
||||
})
|
||||
|
||||
def recomputeMemory() = if (lua != null) {
|
||||
def recomputeMemory() = if (lua != null)
|
||||
lua.setTotalMemory(kernelMemory + Config.baseMemory + owner.installedMemory)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// IComputer
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
def start() = stateMonitor.synchronized(
|
||||
@ -157,15 +99,11 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
// Mark state change in owner, to send it to clients.
|
||||
owner.markAsChanged()
|
||||
|
||||
// Inject a dummy signal so that real ones don't get swallowed. This way
|
||||
// we can just ignore the parameters the first time the kernel is run
|
||||
// and all actual signals will be read using coroutine.yield().
|
||||
signal("")
|
||||
|
||||
// Inject component added signals for all nodes in the network.
|
||||
owner.network.foreach(_.nodes(owner).foreach(node => signal("component_added", node.address.get)))
|
||||
|
||||
// All green, computer started successfully.
|
||||
owner.network.foreach(_.sendToVisible(owner, "computer.started"))
|
||||
true
|
||||
})
|
||||
|
||||
@ -182,7 +120,26 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
true
|
||||
})
|
||||
|
||||
def isRunning = state != Computer.State.Stopped
|
||||
def isRunning = state != Computer.State.Stopped && lastUpdate != 0
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
def signal(name: String, args: Any*) = stateMonitor.synchronized(state match {
|
||||
case Computer.State.Stopped | Computer.State.Stopping => false
|
||||
case _ => signals.offer(new Computer.Signal(name, args.map {
|
||||
case null | Unit | None => Unit
|
||||
case arg: Boolean => arg
|
||||
case arg: Byte => arg.toDouble
|
||||
case arg: Char => arg.toDouble
|
||||
case arg: Short => arg.toDouble
|
||||
case arg: Int => arg.toDouble
|
||||
case arg: Long => arg.toDouble
|
||||
case arg: Float => arg.toDouble
|
||||
case arg: Double => arg
|
||||
case arg: String => arg
|
||||
case _ => throw new IllegalArgumentException()
|
||||
}.toArray))
|
||||
})
|
||||
|
||||
def update() {
|
||||
// Update last time run to let our executor thread know it doesn't have to
|
||||
@ -196,6 +153,11 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
// Update world time for computer threads.
|
||||
worldTime = owner.world.getWorldInfo.getWorldTotalTime
|
||||
|
||||
// Signal stops to the network. This is used to close file handles, for example.
|
||||
if (wasRunning && !isRunning)
|
||||
owner.network.foreach(_.sendToVisible(owner, "computer.stopped"))
|
||||
wasRunning = isRunning
|
||||
|
||||
// Check if we should switch states.
|
||||
stateMonitor.synchronized(state match {
|
||||
// Computer is rebooting.
|
||||
@ -308,7 +270,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
}.toArray)
|
||||
}).asJava)
|
||||
|
||||
rom.load(nbt.getCompoundTag("rom"))
|
||||
rom.foreach(_.load(nbt.getCompoundTag("rom")))
|
||||
kernelMemory = nbt.getInteger("kernelMemory")
|
||||
timeStarted = nbt.getLong("timeStarted")
|
||||
|
||||
@ -387,7 +349,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
nbt.setTag("signals", list)
|
||||
|
||||
val romNbt = new NBTTagCompound()
|
||||
rom.save(romNbt)
|
||||
rom.foreach(_.save(romNbt))
|
||||
nbt.setCompoundTag("rom", romNbt)
|
||||
nbt.setInteger("kernelMemory", kernelMemory)
|
||||
nbt.setLong("timeStarted", timeStarted)
|
||||
@ -503,10 +465,10 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
|
||||
// And it's ROM address.
|
||||
lua.pushScalaFunction(lua => {
|
||||
rom.address match {
|
||||
rom.foreach(_.address match {
|
||||
case None => lua.pushNil()
|
||||
case Some(address) => lua.pushString(address)
|
||||
}
|
||||
})
|
||||
1
|
||||
})
|
||||
lua.setField(-2, "romAddress")
|
||||
@ -647,7 +609,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
signals.clear()
|
||||
|
||||
// Connect the ROM node to our owner.
|
||||
owner.network.foreach(_.connect(owner, rom))
|
||||
rom.foreach(rom => owner.network.foreach(_.connect(owner, rom)))
|
||||
|
||||
return true
|
||||
}
|
||||
@ -820,6 +782,78 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
}
|
||||
|
||||
object Computer {
|
||||
|
||||
trait Environment extends Node {
|
||||
protected val computer: Computer
|
||||
|
||||
def world: World
|
||||
|
||||
def installedMemory: Int
|
||||
|
||||
/**
|
||||
* Called when the computer state changed, so it should be saved again.
|
||||
* <p/>
|
||||
* This is called asynchronously from the Computer's executor thread, so the
|
||||
* computer's owner must make sure to handle this in a synchronized fashion.
|
||||
*/
|
||||
def markAsChanged(): Unit
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def name = "computer"
|
||||
|
||||
override def visibility = Visibility.Network
|
||||
|
||||
override def receive(message: Message) = {
|
||||
super.receive(message)
|
||||
message.data match {
|
||||
// The isRunning check is here to avoid component_* signals being
|
||||
// generated while loading a chunk.
|
||||
case Array() if message.name == "network.connect" && computer.isRunning =>
|
||||
computer.signal("component_added", message.source.address.get); None
|
||||
case Array() if message.name == "network.disconnect" && computer.isRunning =>
|
||||
computer.signal("component_removed", message.source.address.get); None
|
||||
case Array(oldAddress: Integer) if message.name == "network.reconnect" && computer.isRunning =>
|
||||
computer.signal("component_changed", message.source.address.get, oldAddress); None
|
||||
|
||||
// Arbitrary signals, usually from other components.
|
||||
case Array(name: String, args@_*) if message.name == "computer.signal" =>
|
||||
computer.signal(name, List(message.source.address.get) ++ args: _*); None
|
||||
|
||||
// Remote control.
|
||||
case Array() if message.name == "computer.start" =>
|
||||
Some(Array(computer.start().asInstanceOf[Any]))
|
||||
case Array() if message.name == "computer.stop" =>
|
||||
Some(Array(computer.stop().asInstanceOf[Any]))
|
||||
case Array() if message.name == "computer.running" =>
|
||||
Some(Array(computer.isRunning.asInstanceOf[Any]))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
override protected def onConnect() {
|
||||
super.onConnect()
|
||||
computer.rom.foreach(rom => network.foreach(_.connect(this, rom)))
|
||||
}
|
||||
|
||||
override protected def onDisconnect() {
|
||||
super.onDisconnect()
|
||||
computer.rom.foreach(rom => rom.network.foreach(_.remove(rom)))
|
||||
}
|
||||
|
||||
override def load(nbt: NBTTagCompound) {
|
||||
super.load(nbt)
|
||||
computer.load(nbt.getCompoundTag("computer"))
|
||||
}
|
||||
|
||||
override def save(nbt: NBTTagCompound) {
|
||||
super.save(nbt)
|
||||
val computerNbt = new NBTTagCompound
|
||||
computer.save(computerNbt)
|
||||
nbt.setCompoundTag("computer", computerNbt)
|
||||
}
|
||||
}
|
||||
|
||||
@ForgeSubscribe
|
||||
def onChunkUnload(e: ChunkEvent.Unload) =
|
||||
onUnload(e.world, e.getChunk.chunkTileEntityMap.values.map(_.asInstanceOf[TileEntity]))
|
||||
@ -831,28 +865,6 @@ object Computer {
|
||||
foreach(_.turnOff())
|
||||
}
|
||||
|
||||
/**
|
||||
* This has to be implemented by owners of computer instances and allows the
|
||||
* computers to access information about the world they live in.
|
||||
*/
|
||||
trait Environment extends Node {
|
||||
override def name = "computer"
|
||||
|
||||
override def visibility = Visibility.Network
|
||||
|
||||
def world: World
|
||||
|
||||
def installedMemory: Int
|
||||
|
||||
/**
|
||||
* Called when the computer state changed, so it should be saved again.
|
||||
*
|
||||
* This is called asynchronously from the Computer's executor thread, so the
|
||||
* computer's owner must make sure to handle this in a synchronized fashion.
|
||||
*/
|
||||
def markAsChanged(): Unit
|
||||
}
|
||||
|
||||
/** Signals are messages sent to the Lua state from Java asynchronously. */
|
||||
private class Signal(val name: String, val args: Array[Any])
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package li.cil.oc.server.driver
|
||||
|
||||
import li.cil.oc.api
|
||||
import li.cil.oc.api.driver.{Block, Item}
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.world.World
|
||||
import scala.Some
|
||||
@ -23,10 +22,10 @@ import scala.collection.mutable.ArrayBuffer
|
||||
*/
|
||||
private[oc] object Registry extends api.detail.DriverAPI {
|
||||
/** The list of registered block drivers. */
|
||||
private val blocks = ArrayBuffer.empty[Block]
|
||||
private val blocks = ArrayBuffer.empty[api.driver.Block]
|
||||
|
||||
/** The list of registered item drivers. */
|
||||
private val items = ArrayBuffer.empty[Item]
|
||||
private val items = ArrayBuffer.empty[api.driver.Item]
|
||||
|
||||
/** Used to keep track of whether we're past the init phase. */
|
||||
var locked = false
|
||||
@ -39,7 +38,7 @@ private[oc] object Registry extends api.detail.DriverAPI {
|
||||
*
|
||||
* @param driver the driver for that block type.
|
||||
*/
|
||||
def add(driver: Block) {
|
||||
def add(driver: api.driver.Block) {
|
||||
if (locked) throw new IllegalStateException("Please register all drivers in the init phase.")
|
||||
if (!blocks.contains(driver)) blocks += driver
|
||||
}
|
||||
@ -52,7 +51,7 @@ private[oc] object Registry extends api.detail.DriverAPI {
|
||||
*
|
||||
* @param driver the driver for that item type.
|
||||
*/
|
||||
def add(driver: Item) {
|
||||
def add(driver: api.driver.Item) {
|
||||
if (locked) throw new IllegalStateException("Please register all drivers in the init phase.")
|
||||
if (!blocks.contains(driver)) items += driver
|
||||
}
|
||||
|
@ -3,16 +3,16 @@ package li.cil.oc.server.fs
|
||||
import java.io
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
import li.cil.oc.api
|
||||
import li.cil.oc.api.network.Node
|
||||
import li.cil.oc.server.component
|
||||
import li.cil.oc.{Config, api}
|
||||
import net.minecraftforge.common.DimensionManager
|
||||
|
||||
class ReadWriteFileSystem(val root: io.File) extends OutputStreamFileSystem with FileOutputStreamFileSystem
|
||||
|
||||
class ReadOnlyFileSystem(val root: io.File) extends InputStreamFileSystem with FileInputStreamFileSystem
|
||||
|
||||
object FileSystem extends api.detail.FileSystemAPI {
|
||||
def fromClass(clazz: Class[_], domain: String, root: String): Option[api.FileSystem] = {
|
||||
override def fromClass(clazz: Class[_], domain: String, root: String): Option[api.FileSystem] = {
|
||||
val codeSource = clazz.getProtectionDomain.getCodeSource
|
||||
if (codeSource == null) return None
|
||||
val file = new java.io.File(codeSource.getLocation.toURI)
|
||||
@ -43,5 +43,13 @@ object FileSystem extends api.detail.FileSystemAPI {
|
||||
}
|
||||
}
|
||||
|
||||
def asNode(fileSystem: api.FileSystem): Option[Node] = Some(new component.FileSystem(fileSystem))
|
||||
override def fromSaveDir(root: String) = {
|
||||
val path = new File(DimensionManager.getCurrentSaveRootDirectory, Config.savePath + root)
|
||||
path.mkdirs()
|
||||
if (path.exists() && path.isDirectory)
|
||||
Some(new ReadWriteFileSystem(path))
|
||||
else None
|
||||
}
|
||||
|
||||
override def asNode(fileSystem: api.FileSystem) = Some(new component.FileSystem(fileSystem))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user