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:
Florian Nücke 2013-10-03 22:33:48 +02:00
parent 2c14ec95b8
commit 0927f076ce
11 changed files with 236 additions and 228 deletions

View File

@ -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()})

View File

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

View File

@ -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/"

View File

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

View File

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

View File

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

View File

@ -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 {

View File

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

View File

@ -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])

View File

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

View File

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