metric ton of fixes. somehow the number of touches files feels too low... anyway, also added option to LuaCallback annotation to mark callbacks as asynchronous (meaning they will be called directly from the executor thread) and synchronized the sh*t out of the network manager in the hopes that not all will crumble to pieces due to sending messages around while someone meddles with the network (program calls sth while player places/breaks block e.g.); oh, and we now take the memory baseline after loading the libs, meaning 32k is enough to run a computer again. also, loading libs is done asynchronously now, since it's loaded from the rom and nobody else should be able to access that anyway, meaning fast boot-times; command line program to manipulate redstone output and read input

This commit is contained in:
Florian Nücke 2013-10-27 15:06:21 +01:00
parent a7ec5c0ec0
commit 78a426c4fe
14 changed files with 361 additions and 218 deletions

View File

@ -53,47 +53,3 @@ end
-- Mark everything that's globally reachable at this point as permanent.
flattenAndStore("_ENV", _ENV)
--[[ Wrap message sending callbacks to synchronize them to the server thread.
We generate a wrapper that will yield a closure that will perform the
actual call. The host will switch to messaging state and perform the
actual in the server thread (by running the yielded function), meaning
we don't have to worry about mutlithreading interaction with other
components of Minecraft.
--]]
-- OK, I admit this is a little crazy... here goes:
local function wrap(f)
assert(f)
-- This is the function that replaces the original API function. It is
-- called from userland when it wants something from a driver.
return function(...)
local args = table.pack(...)
-- What it does, is that it yields a function. That function is called
-- from the server thread, to ensure synchronicity with the world.
local result = coroutine.yield(function()
-- It runs the actual API function protected mode. We return this as
-- a table because a) we need it like this on the outside anyway and
-- b) only the first item in the global stack is persisted.
local result = table.pack(pcall(f, table.unpack(args, 1, args.n)))
if not result[1] then
-- We apply tostring to error messages immediately because JNLua
-- pushes the original Java exceptions which cannot be persisted.
result[2] = tostring(result[2])
end
return result
end)
-- The next time our executor runs it pushes that result and calls
-- resume, so we get it via the yield. Thus: result = pcall(f, ...)
if result[1] then
-- API call was successful, return the results.
return table.unpack(result, 2, result.n)
else
-- API call failed, re-throw the error.
error(result[2], 2)
end
end
end
componentMethodsSynchronized = wrap(componentMethods)
componentInvokeSynchronized = wrap(componentInvoke)

View File

@ -236,36 +236,107 @@ end
-------------------------------------------------------------------------------
local component = {}
component.list = componentList
component.type = componentType
-- We use a custom "protocol" where we return a boolean first, indicating
-- whether we had an error that should be re-thrown.
function component.invokeAsync(...)
local result = table.pack(componentInvoke(...))
if not result[1] then
-- We caught an error but performed some custom tostring logic.
error(result[2], 2)
else
-- We should return the results as they are.
return table.unpack(result, 2, result.n)
end
end
-------------------------------------------------------------------------------
--[[ Wrap message sending callbacks to synchronize them to the server thread.
We generate a wrapper that will yield a closure that will perform the
actual call. The host will switch to messaging state and perform the
actual in the server thread (by running the yielded function), meaning
we don't have to worry about mutlithreading interaction with other
components of Minecraft.
--]]
-- OK, I admit this is a little crazy... here goes:
local function synchronized(f)
-- This is the function that replaces the original API function. It is
-- called from userland when it wants something from a driver.
return function(...)
local args = table.pack(...)
-- What it does, is that it yields a function. That function is called
-- from the server thread, to ensure synchronicity with the world.
local result = coroutine.yield(function()
-- It runs the actual API function protected mode. We return this as
-- a table because a) we need it like this on the outside anyway and
-- b) only the first item in the global stack is persisted.
local result = table.pack(pcall(f, table.unpack(args, 1, args.n)))
if not result[1] then
-- We apply tostring to error messages immediately because JNLua
-- pushes the original Java exceptions which cannot be persisted.
result[2] = tostring(result[2])
end
return result
end)
-- The next time our executor runs it pushes that result and calls
-- resume, so we get it via the yield. Thus: result = pcall(f, ...)
if result[1] then
-- API call was successful, return the results.
return table.unpack(result, 2, result.n)
else
-- API call failed, re-throw the error.
error(result[2], 2)
end
end
end
component.methods = synchronized(componentMethods)
component.invoke = synchronized(component.invokeAsync)
-------------------------------------------------------------------------------
sandbox.component = {}
function sandbox.component.list(filter)
checkArg(1, filter, "string", "nil")
return pairs(componentList(filter))
return pairs(component.list(filter))
end
function sandbox.component.type(address)
checkArg(1, address, "string")
return componentType(address)
return component.type(address)
end
function sandbox.component.invoke(address, method, ...)
checkArg(1, address, "string")
checkArg(2, method, "string")
return componentInvokeSynchronized(address, method, ...)
return component.invoke(address, method, ...)
end
function sandbox.component.proxy(address)
checkArg(1, address, "string")
local type, reason = componentType(address)
local type, reason = component.type(address)
if not type then
return nil, reason
end
local proxy = {address = address, type = type}
local methods = componentMethodsSynchronized(address)
local methods = component.methods(address)
if methods then
for _, method in ipairs(methods) do
for method, asynchronous in pairs(methods) do
local invoke
if asynchronous then
invoke = component.invokeAsync
else
invoke = component.invoke
end
proxy[method] = function(...)
return componentInvokeSynchronized(address, method, ...)
return invoke(address, method, ...)
end
end
end
@ -275,40 +346,14 @@ end
-------------------------------------------------------------------------------
local function main()
local args
local function bootstrap()
-- Prepare low-level print logic to give boot progress feedback.
-- local gpus = gpus()
-- for i = 1, #gpus do
-- local gpu = gpus[i]
-- gpus[i] = nil
-- local w, h = sandbox.driver.gpu.resolution(gpu)
-- if w then
-- if sandbox.driver.gpu.fill(gpu, 1, 1, w, h, " ") then
-- gpus[i] = {gpu, w, h}
-- end
-- end
-- end
-- local l = 0
-- local function print(s)
-- l = l + 1
-- for _, gpu in pairs(gpus) do
-- if l > gpu[3] then
-- sandbox.driver.gpu.copy(gpu[1], 1, 1, gpu[2], gpu[3], 0, -1)
-- sandbox.driver.gpu.fill(gpu[1], 1, gpu[3], gpu[2], 1, " ")
-- end
-- sandbox.driver.gpu.set(gpu[1], 1, math.min(l, gpu[3]), s)
-- end
-- end
print("Booting...")
print("Mounting ROM and temporary file system.")
local function rom(method, ...)
return componentInvoke(os.romAddress(), method, ...)
return component.invokeAsync(os.romAddress(), method, ...)
end
-- Custom dofile implementation since we don't have the baselib yet.
local function dofile(file)
print(" " .. file)
local handle, reason = rom("open", file)
if not handle then
error(reason)
@ -337,7 +382,6 @@ local function main()
end
end
print("Loading libraries.")
local init = {}
for _, api in ipairs(rom("list", "lib")) do
local path = "lib/" .. api
@ -349,32 +393,29 @@ local function main()
end
end
print("Initializing libraries.")
for _, install in ipairs(init) do
install()
end
init = nil
print("Starting shell.")
-- Yield once to allow initializing up to here to get a memory baseline.
args = table.pack(coroutine.yield(0))
return coroutine.create(load([[
fs.mount(os.romAddress(), "/")
fs.mount(os.tmpAddress(), "/tmp")
for c, t in component.list() do
event.fire("component_added", c, t)
end
term.clear()
while true do
local result, reason = os.execute("/bin/sh")
local result, reason = os.execute("/bin/sh -v")
if not result then
error(reason)
end
end]], "=init", "t", sandbox))
end
local co = bootstrap()
-- Yield once to allow initializing up to here to get a memory baseline.
assert(coroutine.yield() == "dummy")
local args = {n=0}
while true do
deadline = os.realTime() + timeout -- timeout global is set by host
if not debug.gethook(co) then

View File

@ -0,0 +1,26 @@
local args = shell.parse(...)
if #args < 1 then
print("Usage: redstone <side> [<value>]")
return
end
local side = rs.sides[args[1]]
if not side then
print("Invalid side.")
end
if type(side) == "string" then
side = rs.sides[side]
end
if #args > 1 then
local value = args[2]
if tonumber(value) then
value = tonumber(value)
else
value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 15 or 0
end
component.primary("redstone").setOutput(side, value)
else
print("in: " .. component.primary("redstone").getInput(side))
print("out: " .. component.primary("redstone").getOutput(side))
end

View File

@ -1,11 +1,19 @@
local args, options = shell.parse(...)
local history = {}
if options.v then
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)")
end
while true do
if not term.isAvailable() then -- don't clear when opened by another shell
if not term.isAvailable() then -- don't clear unless we lost the term
while not term.isAvailable() do
os.sleep()
end
term.clear()
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)")
if options.v then
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)")
end
end
while term.isAvailable() do
term.write("# ")

View File

@ -2,9 +2,10 @@ local function onComponentAvailable(_, componentType)
if (componentType == "screen" and component.isAvailable("gpu")) or
(componentType == "gpu" and component.isAvailable("screen"))
then
component.primary("gpu").bind(component.primary("screen").address)
local gpu = component.primary("gpu")
gpu.bind(component.primary("screen").address)
local maxX, maxY = gpu.maxResolution()
component.primary("gpu").setResolution(maxX, maxY)
gpu.setResolution(maxX, maxY)
end
end

View File

@ -1,7 +1,7 @@
local cwd = "/"
local path = {"/bin/", "/usr/bin/", "/home/bin/"}
local aliases = {dir="ls", move="mv", rename="mv", copy="cp", del="rm",
md="mkdir", cls="clear", more="less"}
md="mkdir", cls="clear", more="less", rs="redstone"}
local function findFile(name, path, ext)
checkArg(1, name, "string")

View File

@ -21,4 +21,15 @@ public @interface LuaCallback {
* @return the name of the function.
*/
String value() default "";
/**
* Whether this function may be called asynchronously, i.e. directly from
* the computer's executor thread.
* <p/>
* You will have to ensure anything your callback does is thread safe when
* setting this to <tt>true</tt>. Use this for minor lookups, for example.
* This is mainly intended to allow functions to perform faster than when
* called synchronously (where the call takes at least one server tick).
*/
boolean asynchronous() default false;
}

View File

@ -50,6 +50,7 @@ trait ComponentInventory extends Inventory with Environment with Persistable {
case Some(driver) =>
Option(driver.createEnvironment(stack)) match {
case Some(environment) =>
environment.node.load(driver.nbt(stack))
environment.load(driver.nbt(stack))
Some(environment)
case _ => None
@ -66,7 +67,9 @@ trait ComponentInventory extends Inventory with Environment with Persistable {
case (stack, slot) => components(slot) match {
case Some(environment) =>
// We're guaranteed to have a driver for entries.
environment.save(Registry.driverFor(stack).get.nbt(stack))
val nbt = Registry.driverFor(stack).get.nbt(stack)
environment.save(nbt)
environment.node.save(nbt)
case _ => // Nothing special to save.
}
}
@ -82,6 +85,7 @@ trait ComponentInventory extends Inventory with Environment with Persistable {
case Some(driver) => Option(driver.createEnvironment(item)) match {
case Some(environment) =>
components(slot) = Some(environment)
environment.node.load(driver.nbt(item))
environment.load(driver.nbt(item))
node.network.connect(node, environment.node)
case _ => // No environment (e.g. RAM).
@ -100,7 +104,11 @@ trait ComponentInventory extends Inventory with Environment with Persistable {
// being installed into a different computer, even!)
components(slot) = None
environment.node.network.remove(environment.node)
Registry.driverFor(item).foreach(driver => environment.save(driver.nbt(item)))
Registry.driverFor(item).foreach(driver => {
val nbt = driver.nbt(item)
environment.save(nbt)
environment.node.save(nbt)
})
case _ => // Nothing to do.
}
}

View File

@ -329,8 +329,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
}.toArray)
})
rom.foreach(_.load(computerNbt.getCompoundTag("rom")))
tmp.foreach(_.load(computerNbt.getCompoundTag("tmp")))
rom.foreach(rom => {
val romNbt = computerNbt.getCompoundTag("rom")
rom.node.load(romNbt)
rom.load(romNbt)
})
tmp.foreach(tmp => {
val tmpNbt = computerNbt.getCompoundTag("tmp")
tmp.node.load(tmpNbt)
tmp.load(tmpNbt)
})
kernelMemory = computerNbt.getInteger("kernelMemory")
timeStarted = computerNbt.getLong("timeStarted")
cpuTime = computerNbt.getLong("cpuTime")
@ -417,11 +425,17 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
computerNbt.setTag("signals", signalsNbt)
val romNbt = new NBTTagCompound()
rom.foreach(_.save(romNbt))
rom.foreach(rom => {
rom.save(romNbt)
rom.node.save(romNbt)
})
computerNbt.setCompoundTag("rom", romNbt)
val tmpNbt = new NBTTagCompound()
tmp.foreach(_.save(tmpNbt))
tmp.foreach(tmp => {
tmp.save(tmpNbt)
tmp.node.save(tmpNbt)
})
computerNbt.setCompoundTag("tmp", tmpNbt)
computerNbt.setInteger("kernelMemory", kernelMemory)
@ -626,11 +640,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
lua.pushScalaFunction(lua => {
owner.node.network.sendToAddress(owner.node, lua.checkString(1), "component.methods") match {
case Array(names: Array[String]) =>
case Array(methods: Array[_]) =>
lua.newTable()
for ((name, index) <- names.zipWithIndex) {
lua.pushString(name)
lua.rawSet(-2, index + 1)
for ((method, index) <- methods.zipWithIndex) {
method match {
case (name: String, asynchronous: Boolean) =>
lua.pushString(name)
lua.pushBoolean(asynchronous)
lua.rawSet(-3)
case _ =>
}
}
1
case _ =>
@ -704,41 +723,52 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
owner.node.network.sendToAddress(owner.node, address, "component.invoke", Seq(method) ++ args: _*)
case _ => throw new Exception("no such component")
}) match {
case Array(results@_*) =>
case results: Array[_] =>
lua.pushBoolean(true)
results.foreach(pushResult(lua, _))
results.length
1 + results.length
case _ =>
0
lua.pushBoolean(true)
1
}
} catch {
case e: Throwable if e.getMessage != null && !e.getMessage.isEmpty =>
lua.pushNil()
lua.pushString(e.getMessage)
2
case _: FileNotFoundException =>
lua.pushNil()
lua.pushString("file not found")
2
case _: SecurityException =>
lua.pushNil()
lua.pushString("access denied")
2
case _: IOException =>
lua.pushNil()
lua.pushString("i/o error")
2
case _: NoSuchMethodException =>
lua.pushNil()
lua.pushBoolean(false)
lua.pushString("no such method")
2
case e: IllegalArgumentException if e.getMessage != null =>
lua.pushBoolean(false)
lua.pushString(e.getMessage)
2
case _: IllegalArgumentException =>
lua.pushNil()
lua.pushBoolean(false)
lua.pushString("bad argument")
2
case e: Throwable if e.getMessage != null =>
lua.pushBoolean(true)
lua.pushNil()
lua.pushString(e.getMessage)
3
case _: FileNotFoundException =>
lua.pushBoolean(true)
lua.pushNil()
lua.pushString("file not found")
3
case _: SecurityException =>
lua.pushBoolean(true)
lua.pushNil()
lua.pushString("access denied")
3
case _: IOException =>
lua.pushBoolean(true)
lua.pushNil()
lua.pushString("i/o error")
3
case _: Throwable =>
lua.pushBoolean(true)
lua.pushNil()
lua.pushString("unknown error")
2
3
}
})
lua.setGlobal("componentInvoke")
@ -771,23 +801,13 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// timeouts.
lua.load(classOf[Computer].getResourceAsStream(Config.scriptPath + "kernel.lua"), "=kernel", "t")
lua.newThread() // Left as the first value on the stack.
// Run to the first yield in kernel, to get a good idea of how much
// memory all the basic functionality we provide needs.
val results = lua.resume(1, 0)
if (lua.status(1) != LuaState.YIELD)
if (!lua.toBoolean(-2)) throw new Exception(lua.toString(-1))
else throw new Exception("kernel return unexpectedly")
lua.pop(results)
// Run the garbage collector to get rid of stuff left behind after the
// initialization phase to get a good estimate of the base memory usage
// the kernel has. We remember that size to grant user-space programs a
// fixed base amount of memory, regardless of the memory need of the
// underlying system (which may change across releases). Add some buffer
// to avoid the init script eating up all the rest immediately.
lua.gc(LuaState.GcAction.COLLECT, 0)
kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + Config.baseMemory
recomputeMemory()
// // Run to the first yield in kernel, to get a good idea of how much
// // memory all the basic functionality we provide needs.
// val results = lua.resume(1, 0)
// if (lua.status(1) != LuaState.YIELD)
// if (!lua.toBoolean(-2)) throw new Exception(lua.toString(-1))
// else throw new Exception("kernel return unexpectedly")
// lua.pop(results)
// Clear any left-over signals from a previous run.
signals.clear()
@ -862,7 +882,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
try {
// Help out the GC a little. The emergency GC has a few limitations that
// will make it free less memory than doing a full step manually.
lua.gc(LuaState.GcAction.COLLECT, 0)
// lua.gc(LuaState.GcAction.COLLECT, 0)
// Resume the Lua state and remember the number of results we get.
cpuStart = System.nanoTime()
val results = if (callReturn) {
@ -887,6 +907,19 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
}
cpuTime += System.nanoTime() - cpuStart
// Check if this was the first run, meaning the one used for initializing
// the kernel (loading the libs, establishing a memory baseline).
if (kernelMemory == 0) {
// Run the garbage collector to get rid of stuff left behind after the
// initialization phase to get a good estimate of the base memory usage
// the kernel has. We remember that size to grant user-space programs a
// fixed base amount of memory, regardless of the memory need of the
// underlying system (which may change across releases).
lua.gc(LuaState.GcAction.COLLECT, 0)
kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + Config.baseMemory
recomputeMemory()
}
// Check if the kernel is still alive.
stateMonitor.synchronized(if (lua.status(1) == LuaState.YIELD) {
// Intermediate state in some cases. Satisfies the assert in execute().

View File

@ -25,7 +25,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
}
}
@LuaCallback("getResolution")
@LuaCallback(value = "getResolution", asynchronous = true)
def getResolution(message: Message): Array[Object] = trySend("screen.resolution")
@LuaCallback("setResolution")
@ -39,7 +39,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
Array(Unit, "unsupported resolution")
}
@LuaCallback("maxResolution")
@LuaCallback(value = "maxResolution", asynchronous = true)
def maxResolution(message: Message): Array[Object] =
trySend("screen.resolution") match {
case Array(w: Integer, h: Integer) =>
@ -62,7 +62,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
val y = message.checkInteger(2)
val w = message.checkInteger(3)
val h = message.checkInteger(4)
val value = message.checkString(4)
val value = message.checkString(5)
if (value.length == 1)
trySend("screen.fill", x - 1, y - 1, w, h, value.charAt(0))
else
@ -96,13 +96,11 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) {
super.load(nbt)
if (nbt.hasKey("oc.gpu.screen"))
screen = Some(nbt.getString("oc.gpu.screen"))
}
override def save(nbt: NBTTagCompound) {
super.save(nbt)
if (screen.isDefined)
nbt.setString("oc.gpu.screen", screen.get)
}

View File

@ -8,14 +8,14 @@ import net.minecraftforge.common.ForgeDirection
class RedstoneCard extends ManagedComponent {
val node = api.Network.createComponent(api.Network.createNode(this, "redstone", Visibility.Neighbors))
@LuaCallback("getInput")
@LuaCallback(value = "getInput", asynchronous = true)
def getInput(message: Message): Array[Object] = {
val side = message.checkInteger(1)
node.network.sendToAddress(node, message.source.address,
"redstone.input", ForgeDirection.getOrientation(side))
}
@LuaCallback("getOutput")
@LuaCallback(value = "getOutput", asynchronous = true)
def getOutput(message: Message): Array[Object] = {
val side = message.checkInteger(1)
node.network.sendToAddress(node, message.source.address,

View File

@ -63,11 +63,13 @@ class Component(host: Environment, name: String, reachability: Visibility) exten
override def receive(message: Message) = {
if (message.name == "component.methods")
if (canBeSeenBy(message.source))
Array(Array(luaCallbacks.keys.toSeq: _*))
Array(luaCallbacks.map {
case (method, (asynchronous, _)) => (method, asynchronous)
}.toArray)
else null
else if (message.name == "component.invoke") {
luaCallbacks.get(message.data()(0).asInstanceOf[String]) match {
case Some(callback) => callback(host, message)
case Some((_, callback)) => callback(host, message)
case _ => throw new NoSuchMethodException()
}
}
@ -76,25 +78,25 @@ class Component(host: Environment, name: String, reachability: Visibility) exten
// ----------------------------------------------------------------------- //
override def save(nbt: NBTTagCompound) {
override def load(nbt: NBTTagCompound) {
super.load(nbt)
if (nbt.hasKey("visibility"))
visibility_ = Visibility.values()(nbt.getInteger("visibility"))
}
override def load(nbt: NBTTagCompound) {
override def save(nbt: NBTTagCompound) {
super.save(nbt)
nbt.setInteger("visibility", visibility_.ordinal())
}
}
object Component {
private val cache = mutable.Map.empty[Class[_], immutable.Map[String, (Object, api.network.Message) => Array[Object]]]
private val cache = mutable.Map.empty[Class[_], immutable.Map[String, (Boolean, (Object, api.network.Message) => Array[Object])]]
def callbacks(clazz: Class[_]) = cache.getOrElseUpdate(clazz, analyze(clazz))
private def analyze(clazz: Class[_]) = {
val callbacks = mutable.Map.empty[String, (Object, api.network.Message) => Array[Object]]
val callbacks = mutable.Map.empty[String, (Boolean, (Object, api.network.Message) => Array[Object])]
var c = clazz
while (c != classOf[Object]) {
val ms = c.getDeclaredMethods
@ -112,7 +114,7 @@ object Component {
throw new IllegalArgumentException("Invalid use of LuaCallback annotation (name must not be null or empty).")
}
else if (!callbacks.contains(a.value)) {
callbacks += a.value -> ((o, e) => try {
callbacks += a.value ->(a.asynchronous(), (o: Object, e: Message) => try {
m.invoke(o, e).asInstanceOf[Array[Object]]
} catch {
case e: InvocationTargetException => throw e.getCause

View File

@ -18,7 +18,7 @@ import scala.collection.mutable.ArrayBuffer
class Network private(private val addressedNodes: mutable.Map[String, Network.Node] = mutable.Map.empty,
private val unaddressedNodes: mutable.ArrayBuffer[Network.Node] = mutable.ArrayBuffer.empty) extends api.network.Network {
def this(node: Node) = {
def this(node: network.Node) = {
this()
addNew(node)
if (node.address != null)
@ -31,14 +31,18 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
// ----------------------------------------------------------------------- //
def connect(nodeA: api.network.Node, nodeB: api.network.Node) = {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot connect a node to itself.")
if (!nodeA.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
if (!nodeB.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
connect0(nodeA.asInstanceOf[network.Node], nodeB.asInstanceOf[network.Node])
}
private def connect0(nodeA: network.Node, nodeB: network.Node) = this.synchronized {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot connect a node to itself.")
val containsA = contains(nodeA)
val containsB = contains(nodeB)
@ -65,19 +69,23 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
}
else false // That connection already exists.
}
else if (containsA) add(oldNodeA, nodeB.asInstanceOf[network.Node])
else add(oldNodeB, nodeA.asInstanceOf[network.Node])
else if (containsA) add(oldNodeA, nodeB)
else add(oldNodeB, nodeA)
}
def disconnect(nodeA: api.network.Node, nodeB: api.network.Node) = {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot disconnect a node from itself.")
if (!nodeA.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
if (!nodeB.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
disconnect0(nodeA.asInstanceOf[network.Node], nodeB.asInstanceOf[network.Node])
}
private def disconnect0(nodeA: network.Node, nodeB: network.Node) = this.synchronized {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot disconnect a node from itself.")
val containsA = contains(nodeA)
val containsB = contains(nodeB)
@ -100,61 +108,95 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
}
}
def remove(node: api.network.Node) = (Option(node.address) match {
case Some(address) => addressedNodes.remove(address)
case _ => unaddressedNodes.indexWhere(_.data == node) match {
case -1 => None
case index => Some(unaddressedNodes.remove(index))
def remove(node: api.network.Node) = {
if (!node.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
remove0(node.asInstanceOf[network.Node])
}
private def remove0(node: network.Node) = this.synchronized {
(Option(node.address) match {
case Some(address) => addressedNodes.remove(address)
case _ => unaddressedNodes.indexWhere(_.data == node) match {
case -1 => None
case index => Some(unaddressedNodes.remove(index))
}
}) match {
case Some(entry) => {
node.asInstanceOf[Node].network = null
val subGraphs = entry.remove()
val targets = Iterable(node) ++ (entry.data.reachability match {
case Visibility.None => Iterable.empty[api.network.Node]
case Visibility.Neighbors => entry.edges.map(_.other(entry).data)
case Visibility.Network => subGraphs.map {
case (addressed, _) => addressed.values.map(_.data)
}.flatten
})
handleSplit(subGraphs)
send(Network.DisconnectMessage(node), targets)
true
}
case _ => false
}
}) match {
case Some(entry) => {
node.asInstanceOf[Node].network = null
val subGraphs = entry.remove()
val targets = Iterable(node) ++ (entry.data.reachability match {
case Visibility.None => Iterable.empty[api.network.Node]
case Visibility.Neighbors => entry.edges.map(_.other(entry).data)
case Visibility.Network => subGraphs.map {
case (addressed, _) => addressed.values.map(_.data)
}.flatten
})
handleSplit(subGraphs)
send(Network.DisconnectMessage(node), targets)
true
}
case _ => false
}
// ----------------------------------------------------------------------- //
def node(address: String) = addressedNodes.get(address) match {
case Some(node) => node.data
case _ => null
def node(address: String) = this.synchronized {
addressedNodes.get(address) match {
case Some(node) => node.data
case _ => null
}
}
def nodes = addressedNodes.values.map(_.data.asInstanceOf[api.network.Node]).asJava
def nodes = this.synchronized(addressedNodes.values.map(_.data.asInstanceOf[api.network.Node]).asJava)
def nodes(reference: api.network.Node) = {
if (!reference.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
nodes0(reference.asInstanceOf[network.Node])
}
private def nodes0(reference: api.network.Node) = {
val referenceNeighbors = neighbors(reference).toSet.asJava
nodes.filter(node => node != reference && (node.reachability == Visibility.Network ||
(node.reachability == Visibility.Neighbors && referenceNeighbors.contains(node)))).asJava
}
def neighbors(node: api.network.Node) = Option(node.address) match {
case Some(address) =>
addressedNodes.get(address) match {
case Some(n) => n.edges.map(_.other(n).data)
case _ => throw new IllegalArgumentException("Node must be in this network.")
}
case None =>
unaddressedNodes.find(_.data == node) match {
case Some(n) => n.edges.map(_.other(n).data)
case _ => throw new IllegalArgumentException("Node must be in this network.")
}
def neighbors(node: api.network.Node) = {
if (!node.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
neighbors0(node.asInstanceOf[network.Node])
}
private def neighbors0(node: network.Node) = this.synchronized {
Option(node.address) match {
case Some(address) =>
addressedNodes.get(address) match {
case Some(n) => n.edges.map(_.other(n).data)
case _ => throw new IllegalArgumentException("Node must be in this network.")
}
case None =>
unaddressedNodes.find(_.data == node) match {
case Some(n) => n.edges.map(_.other(n).data)
case _ => throw new IllegalArgumentException("Node must be in this network.")
}
}
}
// ----------------------------------------------------------------------- //
def sendToAddress(source: api.network.Node, target: String, name: String, data: AnyRef*) = {
def sendToAddress(source: api.network.Node, target: String, name: String, data: AnyRef*): Array[Object] = {
if (!source.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
sendToAddress0(source.asInstanceOf[network.Node], target, name, data: _*)
}
private def sendToAddress0(source: network.Node, target: String, name: String, data: AnyRef*) = this.synchronized {
if (source.network == null || source.network != this)
throw new IllegalArgumentException("Source node must be in this network.")
if (source.address != null) addressedNodes.get(target) match {
@ -165,7 +207,14 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
} else null
}
def sendToNeighbors(source: api.network.Node, name: String, data: AnyRef*) = {
def sendToNeighbors(source: api.network.Node, name: String, data: AnyRef*): Array[Object] = {
if (!source.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
sendToNeighbors0(source.asInstanceOf[network.Node], name, data: _*)
}
private def sendToNeighbors0(source: network.Node, name: String, data: AnyRef*) = this.synchronized {
if (source.network == null || source.network != this)
throw new IllegalArgumentException("Source node must be in this network.")
if (source.address != null)
@ -173,7 +222,14 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
else null
}
def sendToVisible(source: api.network.Node, name: String, data: AnyRef*) = {
def sendToVisible(source: api.network.Node, name: String, data: AnyRef*): Array[Object] = {
if (!source.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
sendToVisible0(source.asInstanceOf[network.Node], name, data: _*)
}
private def sendToVisible0(source: network.Node, name: String, data: AnyRef*) = this.synchronized {
if (source.network == null || source.network != this)
throw new IllegalArgumentException("Source node must be in this network.")
if (source.address != null)
@ -476,12 +532,12 @@ object Network extends api.detail.NetworkAPI {
private def checkCount(count: Int, name: String) =
if (data.length <= count) throw new IllegalArgumentException(
"bad arguments #%d (%s expected, got no value)".
format(count + 1, name))
format(count, name))
private def typeError(index: Int, have: AnyRef, want: String) =
new IllegalArgumentException(
"bad argument #%d (%s expected, got %s)".
format(index + 1, want, typeName(have)))
format(index, want, typeName(have)))
private def typeName(value: AnyRef): String = value match {
case null => "nil"

View File

@ -27,18 +27,21 @@ class TextBuffer(var width: Int, var height: Int) {
* buffer valid as possible if the size decreases, i.e. only data outside the
* new buffer size will be truncated, all data still inside will be copied.
*/
def size_=(value: (Int, Int)): Boolean = if (size != value) {
val (w, h) = value
val newBuffer = Array.fill(h, w)(' ')
(0 until (h min height)) foreach {
y => Array.copy(buffer(y), 0, newBuffer(y), 0, w min width)
def size_=(value: (Int, Int)): Boolean = {
val (iw, ih) = value
val (w, h) = (iw max 1, ih max 1)
if (width != w || height != h) {
val newBuffer = Array.fill(h, w)(' ')
(0 until (h min height)) foreach {
y => Array.copy(buffer(y), 0, newBuffer(y), 0, w min width)
}
buffer = newBuffer
width = w
height = h
true
}
buffer = newBuffer
width = w
height = h
true
else false
}
else false
/**
* String based fill starting at a specified location.