mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-15 02:12:42 -04:00
some more refactoring; switched up driver loading a bit: they're now loaded from the kernel, by it requesting the list of drivers. this way they're loaded into the sandbox (with the network sending function available, in addition to what the normal sandbox can do); doing primary init of kernel in computer init to get a better idea of how much memory our os stuff needs, in particular the driver table; saving the kernel size now, since the baseline is primarily dependent on the kernel, not what we provide aside from that. this way the "user" memory that comes free is now limitable to 16k with much less variance (init takes around 9k, apparently, the rest is free)
This commit is contained in:
parent
d931b6c92f
commit
abddfae2ea
@ -112,8 +112,3 @@ end
|
||||
sendToNode = wrap(sendToNode)
|
||||
sendToAll = wrap(sendToAll)
|
||||
nodeName = wrap(nodeName)
|
||||
|
||||
driver = {}
|
||||
function driver.componentType(id)
|
||||
return nodeName(id)
|
||||
end
|
@ -1,137 +0,0 @@
|
||||
--[[
|
||||
This is the terminal implementation.
|
||||
|
||||
It provides means to interact with a screen that is provided by the actual
|
||||
game in some way.
|
||||
]]
|
||||
|
||||
function write(term, ...)
|
||||
local w, h = term.getSize()
|
||||
local x, y = term.getCursorPosition()
|
||||
local foreground = term.getForeground()
|
||||
local background = term.getBackground()
|
||||
for _, value in ipairs({...}) do
|
||||
local value = tostring(value)
|
||||
for i in 1, value:len() do
|
||||
term.setCharAt(x, y) = value.sub(i, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Creates a new terminal with an internal display buffer. ]]
|
||||
function new(width, height)
|
||||
-- This is the current display buffer of the terminal: an array of lines, which
|
||||
-- are in turn arrays of the displayed characters on that line, as well as
|
||||
-- foreground and background colors (which may or may not be ignored in the
|
||||
-- actual presentation layer).
|
||||
local buffer = {}
|
||||
|
||||
-- This is the size of the buffer, i.e. its width and height.
|
||||
local size = {x = 0, y = 0}
|
||||
|
||||
-- Cursor position and whether to blink or not.
|
||||
local cursor = {x = 1, y = 1, blink = false}
|
||||
|
||||
-- The current foreground and background color, applied when writing.
|
||||
local colors = {foreground = 0xFFFFFF, background = 0x000000}
|
||||
|
||||
--[[ Resizes the internal buffer and trims / expands it as necessary. ]]
|
||||
local function resize(w, h)
|
||||
-- Trim vertical area.
|
||||
for line = size.y, h + 1, -1 do
|
||||
buffer[line] = nil
|
||||
end
|
||||
-- Trim horizontal area.
|
||||
for line = 1, h do
|
||||
local buffer = buffer[line]
|
||||
for column = size.x, w + 1, -1 do
|
||||
buffer[column] = nil
|
||||
end
|
||||
end
|
||||
-- Expand vertical area.
|
||||
for line = size.y + 1, h do
|
||||
buffer[line] = {}
|
||||
end
|
||||
-- Expand horizontal area.
|
||||
for line = 1, h do
|
||||
local buffer = buffer[line]
|
||||
for column = line <= h and size.x + 1 or 1, w do
|
||||
buffer[column] = {" ", 0xFFFFFF, 0x000000}
|
||||
end
|
||||
end
|
||||
-- Store new sizes.
|
||||
size.x = x
|
||||
size.y = y
|
||||
end
|
||||
|
||||
-- Pre-fill the buffer.
|
||||
resize(80, 24)
|
||||
|
||||
-- Checked buffer access for abstraction layer below.
|
||||
local function get(x, y)
|
||||
if x < 1 or x > size.x then
|
||||
error("column out of bounds")
|
||||
elseif y < 1 or y > size.y then
|
||||
error("line out of bounds")
|
||||
else
|
||||
return buffer[x][y]
|
||||
end
|
||||
end
|
||||
|
||||
-- Abstraction layer to avoid direct access to internals while re-using
|
||||
-- function instances.
|
||||
local term = {
|
||||
getSize = function() return size.x, size.y end,
|
||||
getCharAt = function(x, y) return get(x, y)[1] end,
|
||||
setCharAt = function(x, y, char)
|
||||
assert(type(char) == "string" and char:len() == 1,
|
||||
"'char' must be a single character")
|
||||
get(x, y)[1] = char
|
||||
end,
|
||||
getForegroundAt = function(x, y) return get(x, y)[2] end,
|
||||
setForegroundAt = function(x, y, color)
|
||||
assert(type(color) == "number" and color == math.floor(color),
|
||||
"'color' must be an integral number")
|
||||
get(x, y)[2] = color
|
||||
end,
|
||||
getBackgroundAt = function(x, y) return get(x, y)[3] end,
|
||||
setBackgroundAt = function(x, y, color)
|
||||
assert(type(color) == "number" and color == math.floor(color),
|
||||
"'color' must be an integral number")
|
||||
get(x, y)[3] = color
|
||||
end,
|
||||
getCursorPosition = function() return cursor.x, cursor.y end,
|
||||
setCursorPosition = function(x, y)
|
||||
assert(type(x) == "number" and x == math.floor(x),
|
||||
"'x' must be an integral number")
|
||||
assert(x >= 1 and x <= size.x, "column out of bounds")
|
||||
assert(type(y) == "number" and y == math.floor(y),
|
||||
"'y' must be an integral number")
|
||||
assert(y >= 1 and y <= size.y, "line out of bounds")
|
||||
cursor.x = x
|
||||
cursor.y = y
|
||||
end,
|
||||
getCursorBlink = function() return cursor.blink end,
|
||||
setCursorBlink = function(blink)
|
||||
assert(type(blink) == "boolean", "'blink' must be a boolean")
|
||||
cursor.blink = blink
|
||||
end,
|
||||
getForeground = function() return colors.foreground end,
|
||||
setForeground = function(color)
|
||||
assert(type(color) == "number" and color == math.floor(color),
|
||||
"'color' must be an integral number")
|
||||
colors.foreground = color
|
||||
end,
|
||||
getBackground = function() return colors.background end,
|
||||
setBackground = function(color)
|
||||
assert(type(color) == "number" and color == math.floor(color),
|
||||
"'color' must be an integral number")
|
||||
colors.background = color
|
||||
end
|
||||
}
|
||||
return setmetatable({}, {__index = term})
|
||||
end
|
||||
|
||||
|
||||
local function clear()
|
||||
end
|
@ -2,17 +2,57 @@
|
||||
The Disk API, provided by disk components.
|
||||
]]
|
||||
|
||||
mount = function() end
|
||||
umount = function() end
|
||||
driver.disk = {}
|
||||
|
||||
listdir = function(dirname) end
|
||||
remove = _G.os.remove
|
||||
rename = _G.os.rename
|
||||
tmpname = function() end
|
||||
-- We track mounted disks in this table.
|
||||
local fstab = {}
|
||||
|
||||
open = function(filename, mode) end
|
||||
read = function() end
|
||||
write = function(value) end
|
||||
flush = function(file) end
|
||||
close = function(file) end
|
||||
type = function(file) end
|
||||
function driver.disk.mount()
|
||||
end
|
||||
|
||||
function driver.disk.umount()
|
||||
end
|
||||
|
||||
function driver.disk.listdir(dirname)
|
||||
end
|
||||
|
||||
function driver.disk.remove(path)
|
||||
end
|
||||
|
||||
function driver.disk.rename(path)
|
||||
end
|
||||
|
||||
function driver.disk.tmpname()
|
||||
end
|
||||
|
||||
function driver.disk.open(filename, mode)
|
||||
end
|
||||
|
||||
function driver.disk.read()
|
||||
end
|
||||
|
||||
function driver.disk.write(value)
|
||||
end
|
||||
|
||||
function driver.disk.flush(file)
|
||||
end
|
||||
|
||||
function driver.disk.close(file)
|
||||
end
|
||||
|
||||
function driver.disk.type(file)
|
||||
end
|
||||
|
||||
-- Aliases for vanilla Lua.
|
||||
os.remove = driver.disk.remove
|
||||
os.rename = driver.disk.rename
|
||||
os.tmpname = driver.disk.tmpname
|
||||
|
||||
io = {}
|
||||
io.flush = function() end -- does nothing
|
||||
-- TODO io.lines = function(filename) end
|
||||
io.open = driver.disk.open
|
||||
-- TODO io.popen = function(prog, mode) end
|
||||
io.read = driver.disk.read
|
||||
-- TODO io.tmpfile = function() end
|
||||
io.type = driver.disk.type
|
||||
|
@ -25,27 +25,25 @@ function driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty)
|
||||
sendToNode(gpu, "gpu.copy", screen, col, row, w, h, tx, ty)
|
||||
end
|
||||
|
||||
function driver.gpu.bind(gpuAccess, screenAccess)
|
||||
local gpu = type(gpuAccess) == "function" and gpuAccess or function() return gpuAccess end
|
||||
local screen = type(screenAccess) == "function" and screenAccess or function() return screenAccess end
|
||||
function driver.gpu.bind(gpuId, screenId)
|
||||
return {
|
||||
setResolution = function(w, h)
|
||||
driver.gpu.setResolution(gpu(), screen(), w, h)
|
||||
driver.gpu.setResolution(component.address(gpuId), component.address(screenId), w, h)
|
||||
end,
|
||||
getResolution = function()
|
||||
return driver.gpu.getResolution(gpu(), screen())
|
||||
return driver.gpu.getResolution(component.address(gpuId), component.address(screenId))
|
||||
end,
|
||||
getResolutions = function()
|
||||
return driver.gpu.getResolutions(gpu(), screen())
|
||||
return driver.gpu.getResolutions(component.address(gpuId), component.address(screenId))
|
||||
end,
|
||||
set = function(col, row, value)
|
||||
driver.gpu.set(gpu(), screen(), col, row, value)
|
||||
driver.gpu.set(component.address(gpuId), component.address(screenId), col, row, value)
|
||||
end,
|
||||
fill = function(col, ro, w, h, value)
|
||||
driver.gpu.fill(gpu(), screen(), col, ro, w, h, value)
|
||||
driver.gpu.fill(component.address(gpuId), component.address(screenId), col, ro, w, h, value)
|
||||
end,
|
||||
copy = function(col, row, w, h, tx, ty)
|
||||
driver.gpu.copy(gpu(), screen(), col, row, w, h, tx, ty)
|
||||
driver.gpu.copy(component.address(gpuId), component.address(screenId), col, row, w, h, tx, ty)
|
||||
end
|
||||
}
|
||||
end
|
@ -235,9 +235,7 @@ end)
|
||||
local function bindIfPossible()
|
||||
if gpuId > 0 and screenId > 0 then
|
||||
if not boundGpu then
|
||||
local function gpu() return component.address(gpuId) end
|
||||
local function screen() return component.address(screenId) end
|
||||
boundGpu = driver.gpu.bind(gpu, screen)
|
||||
boundGpu = driver.gpu.bind(gpuId, screenId)
|
||||
screenWidth, screenHeight = boundGpu.getResolution()
|
||||
event.fire("term_available")
|
||||
end
|
||||
@ -340,12 +338,12 @@ end
|
||||
write = function(...)
|
||||
local args = {...}
|
||||
local first = true
|
||||
for _, value in ipairs(args) do
|
||||
for i = 1, #args do
|
||||
if not first then
|
||||
term.write(", ")
|
||||
end
|
||||
first = false
|
||||
term.write(value, true)
|
||||
term.write(args[i], true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,20 +1,6 @@
|
||||
--[[ Basic functionality, drives userland and enforces timeouts.
|
||||
|
||||
This is called as the main coroutine by the computer. If this throws, the
|
||||
computer crashes. If this returns, the computer is considered powered off.
|
||||
--]]
|
||||
|
||||
--[[ Will be adjusted by the kernel when running, represents how long we can
|
||||
continue running without yielding. Used in the debug hook that enforces
|
||||
this timeout by throwing an error if it's exceeded. ]]
|
||||
local deadline = 0
|
||||
|
||||
--[[ The hook installed for process coroutines enforcing the timeout. ]]
|
||||
local function timeoutHook()
|
||||
if os.realTime() > deadline then
|
||||
error({timeout = debug.traceback(2)}, 0)
|
||||
end
|
||||
end
|
||||
--[[ This is called as the main coroutine by the host. If this returns the
|
||||
computer crashes. It should never ever return "normally", only when an
|
||||
error occurred. Shutdown / reboot are signalled via special yields. ]]
|
||||
|
||||
--[[ Set up the global environment we make available to userland programs. ]]
|
||||
local sandbox = {
|
||||
@ -39,17 +25,9 @@ local sandbox = {
|
||||
tonumber = tonumber,
|
||||
tostring = tostring,
|
||||
|
||||
-- We don't care what users do with metatables. The only raised concern was
|
||||
-- about breaking an environment, which is pretty much void in Lua 5.2.
|
||||
getmetatable = getmetatable,
|
||||
setmetatable = setmetatable,
|
||||
|
||||
write = function() end,
|
||||
|
||||
checkArg = checkArg,
|
||||
component = component,
|
||||
driver = driver,
|
||||
|
||||
bit32 = {
|
||||
arshift = bit32.arshift,
|
||||
band = bit32.band,
|
||||
@ -142,114 +120,130 @@ local sandbox = {
|
||||
unpack = table.unpack
|
||||
}
|
||||
}
|
||||
sandbox.checkArg = checkArg
|
||||
sandbox._G = sandbox
|
||||
|
||||
-- Note: 'write' will be replaced by init script.
|
||||
function sandbox.write(...) end
|
||||
function sandbox.print(...)
|
||||
sandbox.write(...)
|
||||
sandbox.write("\n")
|
||||
end
|
||||
|
||||
-- Make the sandbox its own globals table.
|
||||
sandbox._G = sandbox
|
||||
|
||||
-- Allow sandboxes to load code, but only in text form, and in the sandbox.
|
||||
-- Note that we allow passing a custom environment, because if this is called
|
||||
-- from inside the sandbox, env must already be in the sandbox.
|
||||
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. For now this is used for driver
|
||||
calls, in which case the first function is the function performing the
|
||||
actual driver call, but is called from the server thread, and for sleep
|
||||
notifications, i.e. letting the host know we're in no hurry to be
|
||||
called again any time soon. See os.sleep for more on the second.
|
||||
returned by yields for internal stuff. Used for sleeping and message
|
||||
calls (sendToNode and its ilk) that happen synchronized (Server thread).
|
||||
--]]
|
||||
function sandbox.coroutine.yield(...)
|
||||
return coroutine.yield(nil, ...)
|
||||
end
|
||||
function sandbox.coroutine.resume(co, ...)
|
||||
local function checkDeadline()
|
||||
local deadline = 0
|
||||
|
||||
local function checkDeadline()
|
||||
if os.realTime() > deadline then
|
||||
error("too long without yielding", 0)
|
||||
end
|
||||
end
|
||||
local args = {...}
|
||||
end
|
||||
|
||||
local function main(co)
|
||||
local args = {}
|
||||
while true do
|
||||
-- Don't reset counter when resuming, to avoid circumventing the timeout.
|
||||
deadline = os.realTime() + 3
|
||||
if not debug.gethook(co) then
|
||||
debug.sethook(co, checkDeadline, "", 10000)
|
||||
end
|
||||
|
||||
-- Run the coroutine.
|
||||
local result = {coroutine.resume(co, table.unpack(args))}
|
||||
|
||||
-- Check if we're over the deadline since I'm pretty sure the counter of
|
||||
-- coroutines is separate.
|
||||
checkDeadline()
|
||||
|
||||
-- See what kind of yield we have.
|
||||
if result[1] then
|
||||
-- Coroutine returned normally, if we yielded it may be a system yield.
|
||||
if coroutine.status(co) ~= "dead" and result[2] then
|
||||
-- Propagate system yields and repeat the retry.
|
||||
args = coroutine.yield(table.unpack(result, 2))
|
||||
args = {coroutine.yield(result[2])} -- system yielded value
|
||||
else
|
||||
-- Normal yield or coroutine returned, return result.
|
||||
return result[1], table.unpack(result, 3)
|
||||
error(result[2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function sandbox.coroutine.resume(co, ...)
|
||||
local args = {...}
|
||||
while true do
|
||||
if not debug.gethook(co) then -- don't reset counter
|
||||
debug.sethook(co, checkDeadline, "", 10000)
|
||||
end
|
||||
local result = {coroutine.resume(co, table.unpack(args))}
|
||||
checkDeadline()
|
||||
if result[1] then
|
||||
local isSystemYield = coroutine.status(co) ~= "dead" and result[2] ~= nil
|
||||
if isSystemYield then
|
||||
args = coroutine.yield(result[2])
|
||||
else
|
||||
-- Error while executing coroutine.
|
||||
return true, table.unpack(result, 3)
|
||||
end
|
||||
else -- error: result = (bool, string)
|
||||
return table.unpack(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Pull a signal with an optional timeout. ]]
|
||||
function sandbox.coroutine.yield(...)
|
||||
return coroutine.yield(nil, ...)
|
||||
end
|
||||
|
||||
function sandbox.os.signal(name, timeout)
|
||||
checkArg(1, name, "string", "nil")
|
||||
checkArg(2, timeout, "number", "nil")
|
||||
local deadline = os.clock() + (timeout or math.huge)
|
||||
while os.clock() < deadline do
|
||||
local signal = {coroutine.yield(deadline - os.clock())}
|
||||
if signal and (signal[1] == name or name == nil) then
|
||||
local waitUntil = os.clock() + (timeout or math.huge)
|
||||
while os.clock() < waitUntil do
|
||||
local signal = {coroutine.yield(waitUntil - os.clock())}
|
||||
if signal and (name == signal[1] or name == nil) then
|
||||
return table.unpack(signal)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Shutdown the computer. ]]
|
||||
function sandbox.os.shutdown()
|
||||
coroutine.yield(false)
|
||||
end
|
||||
|
||||
--[[ Reboot the computer. ]]
|
||||
function sandbox.os.reboot()
|
||||
coroutine.yield(true)
|
||||
end
|
||||
|
||||
sandbox.driver = {}
|
||||
|
||||
function sandbox.driver.componentType(id)
|
||||
return nodeName(id)
|
||||
end
|
||||
|
||||
do
|
||||
local env = setmetatable({
|
||||
sendToNode = sendToNode,
|
||||
sendToAll = sendToAll
|
||||
}, { __index = sandbox })
|
||||
for name, code in pairs(drivers()) do
|
||||
local driver, reason = load(code, "=" .. name, "t", env)
|
||||
if not driver then
|
||||
print("Failed loading driver '" .. name .. "': " .. reason)
|
||||
else
|
||||
local result, reason = pcall(driver)
|
||||
if not result then
|
||||
print("Failed initializing driver '" .. name .. "': " .. reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Load init script in sandboxed environment.
|
||||
local coinit
|
||||
do
|
||||
local result, reason = load(init(), "=init", "t", sandbox)
|
||||
if not result then
|
||||
error(reason)
|
||||
end
|
||||
coinit = coroutine.create(result)
|
||||
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(function()
|
||||
-- Replace init script code with loaded, sandboxed and threaded script.
|
||||
local init = (function()
|
||||
local result, reason = load(init(), "=init", "t", sandbox)
|
||||
if not result then error(reason, 0) end
|
||||
return coroutine.create(result)
|
||||
end)()
|
||||
|
||||
-- Main kernel loop.
|
||||
local data = {}
|
||||
while true do
|
||||
deadline = os.realTime() + 3
|
||||
local result = {coroutine.resume(init, table.unpack(data))}
|
||||
if result[1] then
|
||||
-- Init should never return, so we have a yield. The first yielded
|
||||
-- value can only be set by system yields.
|
||||
result = result[2]
|
||||
else
|
||||
-- Some other error, go kill ourselves.
|
||||
error(result[2])
|
||||
end
|
||||
data = {coroutine.yield(result)}
|
||||
end
|
||||
end)
|
||||
return pcall(main, coinit)
|
@ -5,34 +5,34 @@ driver.redstone = {}
|
||||
driver.redstone.sides = {"top", "bottom", "left", "right", "front", "back"}
|
||||
|
||||
-- Add inverse mapping and aliases.
|
||||
for k, v in pairs(sides) do sides[v] = k end
|
||||
sides.up = sides.top
|
||||
sides.down = sides.bottom
|
||||
for k, v in pairs(driver.redstone.sides) do
|
||||
driver.redstone.sides[v] = k
|
||||
end
|
||||
driver.redstone.sides.up = driver.redstone.sides.top
|
||||
driver.redstone.sides.down = driver.redstone.sides.bottom
|
||||
|
||||
function driver.redstone.getAnalogInput(card, side)
|
||||
checkArg(1, side, "number")
|
||||
sendToNode(card, os.address(), "redstone.input", side)
|
||||
local safeOsAddress = os.address
|
||||
|
||||
function driver.redstone.analogInput(card, side)
|
||||
sendToNode(card, safeOsAddress(), "redstone.input", side)
|
||||
end
|
||||
|
||||
function driver.redstone.getAnalogOutput(card, side)
|
||||
checkArg(1, side, "number")
|
||||
sendToNode(card, os.address(), "redstone.output", side)
|
||||
function driver.redstone.analogOutput(card, side, value)
|
||||
if value then
|
||||
sendToNode(card, safeOsAddress(), "redstone.output=", side, value)
|
||||
else
|
||||
return sendToNode(card, safeOsAddress(), "redstone.output", side)
|
||||
end
|
||||
end
|
||||
|
||||
function driver.redstone.setAnalogOutput(card, side, value)
|
||||
checkArg(1, side, "number")
|
||||
checkArg(2, side, "number")
|
||||
sendToNode(card, os.address(), "redstone.output=", side, value)
|
||||
function driver.redstone.input(card, side)
|
||||
return driver.redstone.analogInput(card, side) > 0
|
||||
end
|
||||
|
||||
function getInput(side)
|
||||
return getAnalogInput(side) > 0
|
||||
end
|
||||
|
||||
function getOutput(side)
|
||||
return getAnalogOutput(side) > 0
|
||||
end
|
||||
|
||||
function setOutput(side, value)
|
||||
rs.setAnalogOutput(side, value and 15 or 0)
|
||||
function output(card, side, value)
|
||||
if value then
|
||||
driver.redstone.analogOutput(side, value and 15 or 0)
|
||||
else
|
||||
return driver.redstone.analogOutput(card, side) > 0
|
||||
end
|
||||
end
|
||||
|
56
li/cil/oc/api/Driver.scala
Normal file
56
li/cil/oc/api/Driver.scala
Normal file
@ -0,0 +1,56 @@
|
||||
package li.cil.oc.api
|
||||
|
||||
import java.io.InputStream
|
||||
import li.cil.oc.api.driver.{Item, Block}
|
||||
|
||||
/**
|
||||
* This interface specifies the structure of a driver for a component.
|
||||
* <p/>
|
||||
* A driver is essentially the glue code that allows arbitrary objects to be
|
||||
* used as computer components. They specify an API that is injected into the
|
||||
* Lua state when the driver is installed, and provide general information used
|
||||
* by the computer.
|
||||
* <p/>
|
||||
* Do not implement this interface directly; use the `ItemDriver` and
|
||||
* `BlockDriver` interfaces for the respective component types.
|
||||
*/
|
||||
trait Driver {
|
||||
/**
|
||||
* The code that is run when the driver is installed.
|
||||
* <p/>
|
||||
* The code will be run in a 'privileged' sandbox, i.e the basic environment
|
||||
* is already the sandboxed environment (thus any changes made to the globals
|
||||
* table will be noticeable - keep copies of security relevant functions). In
|
||||
* addition to the sandbox it will also have access to two more functions,
|
||||
* `sendToNode` and `sendToAll`, which will send a (synchronized) message to
|
||||
* the component network the computer is attached to.
|
||||
* <p/>
|
||||
* Drivers must take all precautions to avoid these two functions "leaking"
|
||||
* into user-space; at that point the user is pretty much in "creative mode",
|
||||
* relatively speaking (i.e. the user could send arbitrary messages into the
|
||||
* network, which is really not a good idea).
|
||||
* <p/>
|
||||
* This can be `None` to do nothing. Otherwise this is expected to be valid
|
||||
* Lua code (it is loaded via <code>load()</code> and then run).
|
||||
* <p/>
|
||||
* The stream has to be recreated each time this is called. Normally you will
|
||||
* return something along the lines of
|
||||
* `Mod.class.getResourceAsStream("/assets/yourmod/ocapi.lua")`
|
||||
* from this method. If you wish to hard-code the returned script, you can
|
||||
* use `new ByteArrayInputStream(yourScript.getBytes())` instead.
|
||||
* <p/>
|
||||
* IMPORTANT: Note that the stream will automatically closed.
|
||||
*
|
||||
* @return the Lua code to run when a computer is started up.
|
||||
*/
|
||||
def api: Option[InputStream] = None
|
||||
}
|
||||
|
||||
object Driver {
|
||||
/** Initialized in pre-init. */
|
||||
private[oc] var registry: Option[{def add(driver: Block); def add(driver: Item)}] = None
|
||||
|
||||
def add(driver: Block) = registry.foreach(_.add(driver))
|
||||
|
||||
def add(driver: Item) = registry.foreach(_.add(driver))
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
package li.cil.oc.api.network
|
||||
package li.cil.oc.api
|
||||
|
||||
import li.cil.oc.api.network.Node
|
||||
import net.minecraft.world.IBlockAccess
|
||||
|
||||
/**
|
||||
* Interface for interacting with component networks.
|
||||
@ -12,15 +15,15 @@ package li.cil.oc.api.network
|
||||
* neighbors to see if a network already exists. If so, it should join that
|
||||
* network. If multiple different networks are adjacent it should join one and
|
||||
* then merge it with the other(s). If no networks exist, it should create a new
|
||||
* one. All this logic is provided by `NetworkAPI#joinOrCreateNetwork`.
|
||||
* one. All this logic is provided by `Network.joinOrCreateNetwork`.
|
||||
* <p/>
|
||||
* Note that for network nodes implemented in <tt>TileEntities</tt> adding and
|
||||
* removal is automatically provided on chunk load and unload. When a block is
|
||||
* placed or broken you will have to implement this logic yourself (i.e. call
|
||||
* <tt>NetworkAPI.joinOrCreateNetwork</tt> in <tt>onBlockAdded</tt> and
|
||||
* <tt>getNetwork.remove</tt> in <tt>breakBlock</tt>.
|
||||
* <tt>Network.joinOrCreateNetwork</tt> in <tt>onBlockAdded</tt> and
|
||||
* <tt>Network.remove</tt> in <tt>breakBlock</tt>.
|
||||
* <p/>
|
||||
* All other kinds of nodes have to be managed manually. See `INetworkNode`.
|
||||
* All other kinds of nodes have to be managed manually. See `Node`.
|
||||
* <p/>
|
||||
* There are a couple of system messages to be aware of. These are all sent by
|
||||
* the network manager itself:
|
||||
@ -38,7 +41,7 @@ package li.cil.oc.api.network
|
||||
* instances of your own network implementation; this will lead to
|
||||
* incompatibilities with the built-in network implementation (which can only
|
||||
* merge with other networks of its own type). Always use the methods provided
|
||||
* in <tt>NetworkAPI</tt> to create and join networks.
|
||||
* in <tt>Network</tt> to create and join networks.
|
||||
*/
|
||||
trait Network {
|
||||
/**
|
||||
@ -59,10 +62,6 @@ trait Network {
|
||||
* @return true if a new connection between the two nodes was added; false if
|
||||
* the connection already existed.
|
||||
* @throws IllegalArgumentException if neither node is in this network.
|
||||
* @throws IllegalStateException if this is called while the network is
|
||||
* already updating, for example from
|
||||
* `INetworkNode#receive` (which may be
|
||||
* called during the update).
|
||||
*/
|
||||
def connect(nodeA: Node, nodeB: Node): Boolean
|
||||
|
||||
@ -216,3 +215,19 @@ trait Network {
|
||||
*/
|
||||
def sendToAll(source: Node, name: String, data: Any*)
|
||||
}
|
||||
|
||||
object Network {
|
||||
/** Initialized in pre-init. */
|
||||
private[oc] var network: Option[{def joinOrCreateNetwork(world: IBlockAccess, x: Int, y: Int, z: Int)}] = None
|
||||
|
||||
/**
|
||||
* Tries to add a tile entity network node at the specified coordinates to adjacent networks.
|
||||
*
|
||||
* @param world the world the tile entity lives in.
|
||||
* @param x the X coordinate of the tile entity.
|
||||
* @param y the Y coordinate of the tile entity.
|
||||
* @param z the Z coordinate of the tile entity.
|
||||
*/
|
||||
def joinOrCreateNetwork(world: IBlockAccess, x: Int, y: Int, z: Int) =
|
||||
network.foreach(_.joinOrCreateNetwork(world, x, y, z))
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package li.cil.oc.api.driver
|
||||
|
||||
import li.cil.oc.server.computer.Drivers
|
||||
|
||||
object API {
|
||||
def addDriver(driver: Block) {
|
||||
Drivers.add(driver)
|
||||
}
|
||||
|
||||
def addDriver(driver: Item) {
|
||||
Drivers.add(driver)
|
||||
}
|
||||
}
|
@ -2,13 +2,14 @@ package li.cil.oc.api.driver
|
||||
|
||||
import li.cil.oc.api.network.Node
|
||||
import net.minecraft.world.World
|
||||
import li.cil.oc.api.Driver
|
||||
|
||||
/**
|
||||
* Interface for block component drivers.
|
||||
* <p/>
|
||||
* This driver type is used for components that are blocks, i.e. that can be
|
||||
* placed in the world, but cannot be modified to or don't want to have their
|
||||
* `TileEntities` implement `INetworkNode`.
|
||||
* `TileEntities` implement `network.Node`.
|
||||
* <p/>
|
||||
* A block driver is used by proxy blocks to check its neighbors and whether
|
||||
* those neighbors should be treated as components or not.
|
||||
|
@ -1,45 +0,0 @@
|
||||
package li.cil.oc.api.driver
|
||||
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* This interface specifies the structure of a driver for a component.
|
||||
* <p/>
|
||||
* A driver is essentially the glue code that allows arbitrary objects to be
|
||||
* used as computer components. They specify an API that is injected into the
|
||||
* Lua state when the driver is installed, and provide general information used
|
||||
* by the computer.
|
||||
* <p/>
|
||||
* Do not implement this interface directly; use the `IItemDriver` and
|
||||
* `IBlockDriver` interfaces for the respective component types.
|
||||
*/
|
||||
trait Driver {
|
||||
/**
|
||||
* Some initialization code that is run when the driver is installed.
|
||||
* <p/>
|
||||
* These will usually be some functions that generate network messages of
|
||||
* the particular signature the node of the driver handles, but may contain
|
||||
* arbitrary other functions. However, whatever you do, keep in mind that
|
||||
* only certain parts of the global namespace will be made available to the
|
||||
* computer at runtime, so it's best to keep all you declare in the driver
|
||||
* table (global variable `driver`).
|
||||
* <p/>
|
||||
* This is loaded into the Lua state and run in the global, un-sandboxed
|
||||
* environment. This means your scripts can mess things up bad, so make sure
|
||||
* you know what you're doing and exposing.
|
||||
* <p/>
|
||||
* This can be `None` to do nothing. Otherwise this is expected to be valid
|
||||
* Lua code (it is simply loaded via <code>load()</code> and then executed).
|
||||
* <p/>
|
||||
* The stream has to be recreated each time this is called. Normally you will
|
||||
* return something along the lines of
|
||||
* `Mod.class.getResourceAsStream("/assets/yourmod/lua/ocapi.lua")`
|
||||
* from this method. If you wish to hard-code the returned script, you can use
|
||||
* `new ByteArrayInputStream(yourScript.getBytes())` instead.
|
||||
* <p/>
|
||||
* IMPORTANT: Note that the stream will automatically closed.
|
||||
*
|
||||
* @return the Lua code to run when a computer is started up.
|
||||
*/
|
||||
def api: Option[InputStream] = None
|
||||
}
|
@ -2,6 +2,7 @@ package li.cil.oc.api.driver
|
||||
|
||||
import li.cil.oc.api.network.Node
|
||||
import net.minecraft.item.ItemStack
|
||||
import li.cil.oc.api.Driver
|
||||
|
||||
/**
|
||||
* Interface for item component drivers.
|
||||
@ -13,9 +14,9 @@ import net.minecraft.item.ItemStack
|
||||
* When trying to add an item to a computer the list of registered drivers is
|
||||
* queried using the drivers' `worksWith` functions. The first driver that
|
||||
* replies positively and whose check against the slot type is successful, i.e.
|
||||
* for which the `componentType` matches the slot, will be used as the
|
||||
* component's driver and the component will be added. If no driver is found
|
||||
* the item will be rejected and cannot be installed.
|
||||
* for which the `slot` matches the slot it should be inserted into, will be
|
||||
* used as the component's driver and the component will be added. If no driver
|
||||
* is found the item will be rejected and cannot be installed.
|
||||
* <p/>
|
||||
* Note that it is possible to write one driver that supports as many different
|
||||
* items as you wish. I'd recommend writing one per device (type), though, to
|
||||
|
@ -1,21 +0,0 @@
|
||||
package li.cil.oc.api.network
|
||||
|
||||
import li.cil.oc.server
|
||||
import net.minecraft.world.IBlockAccess
|
||||
|
||||
/**
|
||||
* Provides convenience methods for interacting with component networks.
|
||||
*/
|
||||
object API {
|
||||
/**
|
||||
* Tries to add a tile entity network node at the specified coordinates to adjacent networks.
|
||||
*
|
||||
* @param world the world the tile entity lives in.
|
||||
* @param x the X coordinate of the tile entity.
|
||||
* @param y the Y coordinate of the tile entity.
|
||||
* @param z the Z coordinate of the tile entity.
|
||||
*/
|
||||
def joinOrCreateNetwork(world: IBlockAccess, x: Int, y: Int, z: Int) =
|
||||
// TODO reflection
|
||||
server.network.Network.joinOrCreateNetwork(world, x, y, z)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package li.cil.oc.api.network
|
||||
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
import li.cil.oc.api.Network
|
||||
|
||||
/**
|
||||
* A single node in a `INetwork`.
|
||||
@ -83,7 +84,7 @@ trait Node {
|
||||
*
|
||||
* @return the network the node is in.
|
||||
*/
|
||||
var network: Network = null
|
||||
var network: Option[Network] = None
|
||||
|
||||
/**
|
||||
* Makes the node handle a message.
|
||||
|
@ -4,10 +4,12 @@ import cpw.mods.fml.common.event._
|
||||
import cpw.mods.fml.common.network.NetworkRegistry
|
||||
import cpw.mods.fml.common.registry.LanguageRegistry
|
||||
import li.cil.oc._
|
||||
import li.cil.oc.api.driver.API
|
||||
import li.cil.oc.server.computer.{Computer, Drivers}
|
||||
import li.cil.oc.api.Driver
|
||||
import li.cil.oc.api.Network
|
||||
import li.cil.oc.server.computer.Computer
|
||||
import li.cil.oc.server.driver
|
||||
import li.cil.oc.server.network.Network
|
||||
import li.cil.oc.server.driver.Registry
|
||||
import li.cil.oc.server.network
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
|
||||
class Proxy {
|
||||
@ -16,6 +18,9 @@ class Proxy {
|
||||
|
||||
LanguageRegistry.instance.loadLocalization(
|
||||
"/assets/opencomputers/lang/en_US.lang", "en_US", false)
|
||||
|
||||
Driver.registry = Some(driver.Registry)
|
||||
Network.network = Some(network.Network)
|
||||
}
|
||||
|
||||
def init(e: FMLInitializationEvent): Unit = {
|
||||
@ -24,17 +29,17 @@ class Proxy {
|
||||
|
||||
NetworkRegistry.instance.registerGuiHandler(OpenComputers, GuiHandler)
|
||||
|
||||
API.addDriver(driver.GraphicsCard)
|
||||
API.addDriver(driver.Keyboard)
|
||||
Driver.add(driver.GraphicsCard)
|
||||
Driver.add(driver.Keyboard)
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(Computer)
|
||||
MinecraftForge.EVENT_BUS.register(Network)
|
||||
MinecraftForge.EVENT_BUS.register(network.Network)
|
||||
}
|
||||
|
||||
def postInit(e: FMLPostInitializationEvent): Unit = {
|
||||
// Lock the driver registry to avoid drivers being added after computers
|
||||
// may have already started up. This makes sure the driver API won't change
|
||||
// over the course of a game, since that could lead to weird effects.
|
||||
Drivers.locked = true
|
||||
Registry.locked = true
|
||||
}
|
||||
}
|
@ -3,7 +3,8 @@ package li.cil.oc.common.block
|
||||
import cpw.mods.fml.common.registry.GameRegistry
|
||||
import li.cil.oc.Config
|
||||
import li.cil.oc.CreativeTab
|
||||
import li.cil.oc.api.network.{Node, API}
|
||||
import li.cil.oc.api.Network
|
||||
import li.cil.oc.api.network.Node
|
||||
import li.cil.oc.common.tileentity.TileEntityRotatable
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.material.Material
|
||||
@ -102,7 +103,7 @@ class Multi(id: Int) extends Block(id, Material.iron) {
|
||||
case None => // Invalid but avoid match error.
|
||||
case Some(subBlock) => {
|
||||
world.getBlockTileEntity(x, y, z) match {
|
||||
case node: Node => node.network.remove(node)
|
||||
case node: Node => node.network.foreach(_.remove(node))
|
||||
}
|
||||
subBlock.breakBlock(world, x, y, z, blockId, metadata)
|
||||
}
|
||||
@ -266,7 +267,7 @@ class Multi(id: Int) extends Block(id, Material.iron) {
|
||||
case None => // Invalid but avoid match error.
|
||||
case Some(subBlock) => {
|
||||
world.getBlockTileEntity(x, y, z) match {
|
||||
case _: Node => API.joinOrCreateNetwork(world, x, y, z)
|
||||
case _: Node => Network.joinOrCreateNetwork(world, x, y, z)
|
||||
}
|
||||
subBlock.onBlockAdded(world, x, y, z)
|
||||
}
|
||||
|
@ -52,9 +52,8 @@ trait ScreenEnvironment extends Node {
|
||||
|
||||
}
|
||||
|
||||
def onScreenResolutionChange(w: Int, h: Int) = if (network != null) {
|
||||
network.sendToAll(this, "computer.signal", "screen_resized", w, h)
|
||||
}
|
||||
def onScreenResolutionChange(w: Int, h: Int) =
|
||||
network.foreach(_.sendToAll(this, "computer.signal", "screen_resized", w, h))
|
||||
|
||||
def onScreenSet(col: Int, row: Int, s: String) {}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package li.cil.oc.common.tileentity
|
||||
|
||||
import li.cil.oc.api.network.Node
|
||||
import li.cil.oc.server.computer.Drivers
|
||||
import net.minecraft.inventory.IInventory
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
@ -9,6 +8,7 @@ import net.minecraft.nbt.NBTTagList
|
||||
import net.minecraft.world.World
|
||||
import li.cil.oc.api.driver.Slot
|
||||
import li.cil.oc.common.component.Computer
|
||||
import li.cil.oc.server.driver.Registry
|
||||
|
||||
trait ItemComponentProxy extends IInventory with Node {
|
||||
protected val inventory = new Array[ItemStack](8)
|
||||
@ -52,29 +52,29 @@ trait ItemComponentProxy extends IInventory with Node {
|
||||
// NetworkNode
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override protected def onConnect() = {
|
||||
override protected def onConnect() {
|
||||
super.onConnect()
|
||||
for (slot <- 0 until inventory.length) {
|
||||
itemNode(slot) match {
|
||||
case None => // Ignore.
|
||||
case Some(node) =>
|
||||
network.connect(this, node)
|
||||
network.foreach(_.connect(this, node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override protected def onDisconnect() = {
|
||||
override protected def onDisconnect() {
|
||||
super.onDisconnect()
|
||||
for (slot <- 0 until inventory.length) {
|
||||
itemNode(slot) match {
|
||||
case None => // Ignore.
|
||||
case Some(node) =>
|
||||
node.network.remove(node)
|
||||
node.network.foreach(_.remove(node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def itemNode(slot: Int) = Drivers.driverFor(inventory(slot)) match {
|
||||
private def itemNode(slot: Int) = Registry.driverFor(inventory(slot)) match {
|
||||
case None => None
|
||||
case Some(driver) => driver.node(inventory(slot))
|
||||
}
|
||||
@ -115,7 +115,7 @@ trait ItemComponentProxy extends IInventory with Node {
|
||||
if (!world.isRemote) itemNode(slot) match {
|
||||
case None => // Nothing to do.
|
||||
case Some(node) =>
|
||||
node.network.remove(node)
|
||||
node.network.foreach(_.remove(node))
|
||||
}
|
||||
|
||||
inventory(slot) = item
|
||||
@ -125,11 +125,11 @@ trait ItemComponentProxy extends IInventory with Node {
|
||||
if (!world.isRemote) itemNode(slot) match {
|
||||
case None => // Nothing to do.
|
||||
case Some(node) =>
|
||||
network.connect(this, node)
|
||||
network.foreach(_.connect(this, node))
|
||||
}
|
||||
}
|
||||
|
||||
def isItemValidForSlot(slot: Int, item: ItemStack) = (slot, Drivers.driverFor(item)) match {
|
||||
def isItemValidForSlot(slot: Int, item: ItemStack) = (slot, Registry.driverFor(item)) match {
|
||||
case (_, None) => false // Invalid item.
|
||||
case (0, Some(driver)) => driver.slot(item) == Slot.PSU
|
||||
case (1 | 2 | 3, Some(driver)) => driver.slot(item) == Slot.PCI
|
||||
|
@ -96,11 +96,10 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with com
|
||||
}
|
||||
if (isRunning != computer.isRunning) {
|
||||
isRunning = computer.isRunning
|
||||
if (network != null)
|
||||
if (isRunning)
|
||||
network.sendToAll(this, "computer.started")
|
||||
network.foreach(_.sendToAll(this, "computer.started"))
|
||||
else
|
||||
network.sendToAll(this, "computer.stopped")
|
||||
network.foreach(_.sendToAll(this, "computer.stopped"))
|
||||
ServerPacketSender.sendComputerState(this, isRunning)
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,13 @@ class TileEntityKeyboard extends TileEntityRotatable with Node {
|
||||
message.data match {
|
||||
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" =>
|
||||
if (isUseableByPlayer(p))
|
||||
network.sendToAll(this, "computer.signal", "key_down", char, code)
|
||||
network.foreach(_.sendToAll(this, "computer.signal", "key_down", char, code))
|
||||
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyUp" =>
|
||||
if (isUseableByPlayer(p))
|
||||
network.sendToAll(this, "computer.signal", "key_up", char, code)
|
||||
network.foreach(_.sendToAll(this, "computer.signal", "key_up", char, code))
|
||||
case Array(p: Player, value: String) if message.name == "keyboard.clipboard" =>
|
||||
if (isUseableByPlayer(p))
|
||||
network.sendToAll(this, "computer.signal", "clipboard", value)
|
||||
network.foreach(_.sendToAll(this, "computer.signal", "clipboard", value))
|
||||
case _ => // Ignore.
|
||||
}
|
||||
None
|
||||
|
@ -68,18 +68,18 @@ class PacketHandler extends CommonPacketHandler {
|
||||
def onKeyDown(p: PacketParser) =
|
||||
p.readTileEntity[Node]() match {
|
||||
case None => // Invalid packet.
|
||||
case Some(n) => n.network.sendToNeighbors(n, "keyboard.keyDown", p.player, p.readChar(), p.readInt())
|
||||
case Some(n) => n.network.foreach(_.sendToNeighbors(n, "keyboard.keyDown", p.player, p.readChar(), p.readInt()))
|
||||
}
|
||||
|
||||
def onKeyUp(p: PacketParser) =
|
||||
p.readTileEntity[Node]() match {
|
||||
case None => // Invalid packet.
|
||||
case Some(n) => n.network.sendToNeighbors(n, "keyboard.keyUp", p.player, p.readChar(), p.readInt())
|
||||
case Some(n) => n.network.foreach(_.sendToNeighbors(n, "keyboard.keyUp", p.player, p.readChar(), p.readInt()))
|
||||
}
|
||||
|
||||
def onClipboard(p: PacketParser) =
|
||||
p.readTileEntity[Node]() match {
|
||||
case None => // Invalid packet.
|
||||
case Some(n) => n.network.sendToNeighbors(n, "keyboard.clipboard", p.player, p.readUTF())
|
||||
case Some(n) => n.network.foreach(_.sendToNeighbors(n, "keyboard.clipboard", p.player, p.readUTF()))
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package li.cil.oc.server.component
|
||||
|
||||
import li.cil.oc.api.Network
|
||||
import li.cil.oc.api.network.Message
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
|
||||
@ -10,27 +11,28 @@ class GraphicsCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
|
||||
|
||||
override def receive(message: Message) = {
|
||||
super.receive(message)
|
||||
def trySend(f: Network => Option[Array[Any]]) = network.fold(None: Option[Array[Any]])(f)
|
||||
message.data match {
|
||||
case Array(screen: Double, w: Double, h: Double) if message.name == "gpu.resolution=" =>
|
||||
if (supportedResolutions.contains((w.toInt, h.toInt)))
|
||||
network.sendToAddress(this, screen.toInt, "screen.resolution=", w.toInt, h.toInt)
|
||||
trySend(_.sendToAddress(this, screen.toInt, "screen.resolution=", w.toInt, h.toInt))
|
||||
else Some(Array(None, "unsupported resolution"))
|
||||
case Array(screen: Double) if message.name == "gpu.resolution" =>
|
||||
network.sendToAddress(this, screen.toInt, "screen.resolution")
|
||||
trySend(_.sendToAddress(this, screen.toInt, "screen.resolution"))
|
||||
case Array(screen: Double) if message.name == "gpu.resolutions" =>
|
||||
network.sendToAddress(this, screen.toInt, "screen.resolutions") match {
|
||||
trySend(_.sendToAddress(this, screen.toInt, "screen.resolutions")) match {
|
||||
case Some(Array(resolutions@_*)) =>
|
||||
Some(Array(supportedResolutions.intersect(resolutions): _*))
|
||||
case _ => None
|
||||
}
|
||||
case Array(screen: Double, x: Double, y: Double, value: String) if message.name == "gpu.set" =>
|
||||
network.sendToAddress(this, screen.toInt, "screen.set", x.toInt - 1, y.toInt - 1, value)
|
||||
trySend(_.sendToAddress(this, screen.toInt, "screen.set", x.toInt - 1, y.toInt - 1, value))
|
||||
case Array(screen: Double, x: Double, y: Double, w: Double, h: Double, value: String) if message.name == "gpu.fill" =>
|
||||
if (value != null && value.length == 1)
|
||||
network.sendToAddress(this, screen.toInt, "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, value.charAt(0))
|
||||
trySend(_.sendToAddress(this, screen.toInt, "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, value.charAt(0)))
|
||||
else Some(Array(None, "invalid fill value"))
|
||||
case Array(screen: Double, x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" =>
|
||||
network.sendToAddress(this, screen.toInt, "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
|
||||
trySend(_.sendToAddress(this, screen.toInt, "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package li.cil.oc.server.component
|
||||
|
||||
import li.cil.oc.api.network.Message
|
||||
import li.cil.oc.api.network.Node
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
import net.minecraft.tileentity.TileEntity
|
||||
import net.minecraftforge.common.ForgeDirection
|
||||
@ -21,7 +22,9 @@ class RedstoneCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
|
||||
}
|
||||
}
|
||||
|
||||
private def input(target: Int, side: Int) = if (side >= 0 && side < 6) network.node(target) match {
|
||||
private def tryGet(target: Int) = network.fold(None: Option[Node])(_.node(target))
|
||||
|
||||
private def input(target: Int, side: Int) = if (side >= 0 && side < 6) tryGet(target) match {
|
||||
case Some(r: RedstoneEnabled) => Some(Array(r.input(ForgeDirection.getOrientation(side)).asInstanceOf[Any]))
|
||||
case Some(t: TileEntity) =>
|
||||
val face = ForgeDirection.getOrientation(side.toInt)
|
||||
@ -31,7 +34,7 @@ class RedstoneCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
|
||||
case _ => None // Can't work with this node.
|
||||
} else None
|
||||
|
||||
private def output(target: Int, side: Int) = if (side >= 0 && side < 6) network.node(target) match {
|
||||
private def output(target: Int, side: Int) = if (side >= 0 && side < 6) tryGet(target) match {
|
||||
case Some(r: RedstoneEnabled) => Some(Array(r.output(ForgeDirection.getOrientation(side)).asInstanceOf[Any]))
|
||||
case Some(t: TileEntity) =>
|
||||
val power = t.worldObj.isBlockProvidingPowerTo(t.xCoord, t.yCoord, t.zCoord, side.toInt)
|
||||
@ -39,7 +42,7 @@ class RedstoneCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
|
||||
case _ => None // Can't work with this node.
|
||||
} else None
|
||||
|
||||
private def output(target: Int, side: Int, value: Int) = if (side >= 0 && side < 6) network.node(target) match {
|
||||
private def output(target: Int, side: Int, value: Int) = if (side >= 0 && side < 6) tryGet(target) match {
|
||||
case Some(r: RedstoneEnabled) => r.output(ForgeDirection.getOrientation(side)) = value
|
||||
case _ => // Can't work with this node.
|
||||
}
|
||||
|
@ -5,8 +5,10 @@ import java.lang.Thread.UncaughtExceptionHandler
|
||||
import java.util.concurrent._
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.logging.Level
|
||||
import li.cil.oc.api.network.Node
|
||||
import li.cil.oc.common.component
|
||||
import li.cil.oc.common.tileentity.TileEntityComputer
|
||||
import li.cil.oc.server.driver
|
||||
import li.cil.oc.{OpenComputers, Config}
|
||||
import net.minecraft.nbt._
|
||||
import net.minecraft.tileentity.TileEntity
|
||||
@ -47,7 +49,7 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
private var state = Computer.State.Stopped
|
||||
|
||||
/** The internal Lua state. Only set while the computer is running. */
|
||||
private[computer] var lua: LuaState = null
|
||||
private var lua: LuaState = null
|
||||
|
||||
/**
|
||||
* The base memory consumption of the kernel. Used to permit a fixed base
|
||||
@ -151,7 +153,7 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
signal("")
|
||||
|
||||
// Inject component added signals for all nodes in the network.
|
||||
owner.network.nodes(owner).foreach(node => signal("component_added", node.address))
|
||||
owner.network.foreach(_.nodes(owner).foreach(node => signal("component_added", node.address)))
|
||||
|
||||
// All green, computer started successfully.
|
||||
true
|
||||
@ -220,19 +222,6 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
// to the coroutine.yield() that triggered the call.
|
||||
lua.call(0, 1)
|
||||
lua.checkType(2, LuaType.TABLE)
|
||||
} catch {
|
||||
case _: LuaMemoryAllocationException =>
|
||||
// This can happen if we run out of memory while converting a Java
|
||||
// exception to a string (which we have to do to avoid keeping
|
||||
// userdata on the stack, which cannot be persisted).
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
|
||||
close()
|
||||
case e: Throwable => {
|
||||
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
|
||||
close()
|
||||
}
|
||||
}
|
||||
// Nothing should have been able to trigger a future.
|
||||
assert(future.isEmpty)
|
||||
// If the call lead to stop() being called we stop right now,
|
||||
@ -241,6 +230,19 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
close()
|
||||
else
|
||||
execute(Computer.State.SynchronizedReturn)
|
||||
} catch {
|
||||
case _: LuaMemoryAllocationException =>
|
||||
// This can happen if we run out of memory while converting a Java
|
||||
// exception to a string (which we have to do to avoid keeping
|
||||
// userdata on the stack, which cannot be persisted).
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
owner.network.foreach(_.sendToAll(owner, "computer.crashed", "not enough memory"))
|
||||
close()
|
||||
case e: Throwable => {
|
||||
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
case _ => // Nothing special to do, just avoid match errors.
|
||||
})
|
||||
@ -256,7 +258,6 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
|
||||
if (state != Computer.State.Stopped && init()) {
|
||||
// Unlimit memory use while unpersisting.
|
||||
val memory = lua.getTotalMemory
|
||||
lua.setTotalMemory(Integer.MAX_VALUE)
|
||||
try {
|
||||
// Try unpersisting Lua, because that's what all of the rest depends
|
||||
@ -295,11 +296,12 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
}.toArray)
|
||||
}).asJava)
|
||||
|
||||
kernelMemory = nbt.getInteger("kernelMemory")
|
||||
timeStarted = nbt.getLong("timeStarted")
|
||||
|
||||
// Clean up some after we're done and limit memory again.
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
lua.setTotalMemory(memory)
|
||||
lua.setTotalMemory(kernelMemory + 16 * 1024)
|
||||
|
||||
// Start running our worker thread if we have to (for cases where it
|
||||
// would not be re-started automatically in update()). We start with a
|
||||
@ -363,6 +365,7 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
}
|
||||
nbt.setTag("signals", list)
|
||||
|
||||
nbt.setInteger("kernelMemory", kernelMemory)
|
||||
nbt.setLong("timeStarted", timeStarted)
|
||||
}
|
||||
catch {
|
||||
@ -523,7 +526,8 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
}
|
||||
|
||||
lua.pushJavaFunction(ScalaFunction(lua =>
|
||||
owner.network.sendToAddress(owner, lua.checkInteger(1), lua.checkString(2), parseArguments(lua, 3): _*) match {
|
||||
owner.network.fold(None: Option[Array[Any]])(_.
|
||||
sendToAddress(owner, lua.checkInteger(1), lua.checkString(2), parseArguments(lua, 3): _*)) match {
|
||||
case Some(Array(results@_*)) =>
|
||||
results.foreach(pushResult(lua, _))
|
||||
results.length
|
||||
@ -532,19 +536,44 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
lua.setGlobal("sendToNode")
|
||||
|
||||
lua.pushJavaFunction(ScalaFunction(lua => {
|
||||
owner.network.sendToAll(owner, lua.checkString(1), parseArguments(lua, 2): _*)
|
||||
owner.network.foreach(_.sendToAll(owner, lua.checkString(1), parseArguments(lua, 2): _*))
|
||||
0
|
||||
}))
|
||||
lua.setGlobal("sendToAll")
|
||||
|
||||
lua.pushJavaFunction(ScalaFunction(lua => {
|
||||
owner.network.node(lua.checkInteger(1)) match {
|
||||
owner.network.fold(None: Option[Node])(_.node(lua.checkInteger(1))) match {
|
||||
case None => 0
|
||||
case Some(node) => lua.pushString(node.name); 1
|
||||
}
|
||||
}))
|
||||
lua.setGlobal("nodeName")
|
||||
|
||||
// Provide driver API code.
|
||||
lua.pushJavaFunction(ScalaFunction(lua => {
|
||||
val apis = driver.Registry.apis
|
||||
lua.newTable(apis.length, 0)
|
||||
for ((name, code) <- apis) {
|
||||
lua.pushString(Source.fromInputStream(code).mkString)
|
||||
code.close()
|
||||
lua.setField(-2, name)
|
||||
}
|
||||
1
|
||||
}))
|
||||
lua.setGlobal("drivers")
|
||||
|
||||
// Loads the init script. This is loaded and then run by the kernel as a
|
||||
// separate coroutine to sandbox it and enforce timeouts and sandbox user
|
||||
// scripts.
|
||||
lua.pushJavaFunction(new JavaFunction() {
|
||||
def invoke(lua: LuaState): Int = {
|
||||
lua.pushString(Source.fromInputStream(classOf[Computer].
|
||||
getResourceAsStream("/assets/opencomputers/lua/init.lua")).mkString)
|
||||
1
|
||||
}
|
||||
})
|
||||
lua.setGlobal("init")
|
||||
|
||||
// Run the boot script. This sets up the permanent value tables as
|
||||
// well as making the functions used for persisting/unpersisting
|
||||
// available as globals. It also wraps the message sending functions
|
||||
@ -554,30 +583,15 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
"/assets/opencomputers/lua/boot.lua"), "=boot", "t")
|
||||
lua.call(0, 0)
|
||||
|
||||
// Install all driver callbacks into the state. This is done once in
|
||||
// the beginning so that we can take the memory the callbacks use into
|
||||
// account when computing the kernel's memory use.
|
||||
Drivers.installOn(this)
|
||||
|
||||
// Loads the init script. This is run by the kernel as a separate
|
||||
// coroutine to enforce timeouts and sandbox user scripts.
|
||||
lua.pushJavaFunction(new JavaFunction() {
|
||||
def invoke(lua: LuaState): Int = {
|
||||
lua.pushString(Source.fromInputStream(classOf[Computer].
|
||||
getResourceAsStream("/assets/opencomputers/lua/init.lua")).mkString)
|
||||
1
|
||||
}
|
||||
})
|
||||
lua.setGlobal("init")
|
||||
|
||||
// Load the basic kernel which takes care of handling signals by managing
|
||||
// the list of active processes. Whatever functionality we can we
|
||||
// implement in Lua, so we also implement most of the kernel's
|
||||
// functionality in Lua. Why? Because like this it's automatically
|
||||
// persisted for us without having to write more additional NBT stuff.
|
||||
// Load the basic kernel which sets up the sandbox, loads the init script
|
||||
// and then runs it in a coroutine with a debug hook checking for
|
||||
// timeouts.
|
||||
lua.load(classOf[Computer].getResourceAsStream(
|
||||
"/assets/opencomputers/lua/kernel.lua"), "=kernel", "t")
|
||||
lua.newThread() // Leave it as the first value on the stack.
|
||||
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.
|
||||
lua.pop(lua.resume(1, 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
|
||||
@ -586,7 +600,7 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
// underlying system (which may change across releases).
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
kernelMemory = lua.getTotalMemory - lua.getFreeMemory
|
||||
lua.setTotalMemory(kernelMemory + 64 * 1024)
|
||||
lua.setTotalMemory(kernelMemory + 16 * 1024)
|
||||
|
||||
// Clear any left-over signals from a previous run.
|
||||
signals.clear()
|
||||
@ -731,6 +745,8 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
OpenComputers.log.warning("Kernel stopped unexpectedly.")
|
||||
}
|
||||
else {
|
||||
// This can trigger another out of memory error if the original
|
||||
// error was an out of memory error.
|
||||
OpenComputers.log.warning("Computer crashed.\n" + lua.toString(3)) // TODO remove this when we have a component that can display crash messages
|
||||
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
|
||||
//owner.network.sendToAll(owner, "computer.crashed", lua.toString(3))
|
||||
@ -742,12 +758,16 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
|
||||
case e: LuaRuntimeException =>
|
||||
OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
|
||||
close()
|
||||
case e: LuaMemoryAllocationException => {
|
||||
case e: LuaMemoryAllocationException =>
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
|
||||
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
|
||||
close()
|
||||
case e: java.lang.Error if e.getMessage == "not enough memory" =>
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
|
||||
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
// State has inevitably changed, mark as changed to save again.
|
||||
|
@ -5,7 +5,7 @@ import com.naef.jnlua.{JavaFunction, LuaState, NativeSupport}
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.channels.Channels
|
||||
import java.util.{Locale, Calendar}
|
||||
import java.util.{Formatter, Locale, Calendar}
|
||||
import scala.util.Random
|
||||
|
||||
case class ScalaFunction(f: (LuaState) => Int) extends JavaFunction {
|
||||
@ -217,13 +217,13 @@ private[computer] object LuaStateFactory {
|
||||
}))
|
||||
state.setField(-2, "char")
|
||||
|
||||
// TODO find
|
||||
// TODO find (probably not necessary?)
|
||||
|
||||
// TODO format
|
||||
// TODO format (probably not necessary?)
|
||||
|
||||
// TODO gmatch
|
||||
// TODO gmatch (probably not necessary?)
|
||||
|
||||
// TODO gsub
|
||||
// TODO gsub (probably not necessary?)
|
||||
|
||||
state.pushJavaFunction(ScalaFunction(lua => {
|
||||
lua.pushInteger(lua.checkString(1).length)
|
||||
@ -237,7 +237,7 @@ private[computer] object LuaStateFactory {
|
||||
}))
|
||||
state.setField(-2, "lower")
|
||||
|
||||
// TODO match
|
||||
// TODO match (probably not necessary?)
|
||||
|
||||
state.pushJavaFunction(ScalaFunction(lua => {
|
||||
lua.pushString(lua.checkString(1).reverse)
|
||||
|
@ -1,13 +1,10 @@
|
||||
package li.cil.oc.server.computer
|
||||
package li.cil.oc.server.driver
|
||||
|
||||
import com.naef.jnlua.LuaRuntimeException
|
||||
import li.cil.oc.OpenComputers
|
||||
import li.cil.oc.api.driver.{Block, Item}
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.world.World
|
||||
import scala.Some
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.compat.Platform._
|
||||
|
||||
/**
|
||||
* This class keeps track of registered drivers and provides installation logic
|
||||
@ -23,7 +20,7 @@ import scala.compat.Platform._
|
||||
* usually require a component of the type the driver wraps to be installed in
|
||||
* the computer, but may also provide context-free functions.
|
||||
*/
|
||||
private[oc] object Drivers {
|
||||
private[oc] object Registry {
|
||||
/** The list of registered block drivers. */
|
||||
private val blocks = ArrayBuffer.empty[Block]
|
||||
|
||||
@ -90,30 +87,11 @@ private[oc] object Drivers {
|
||||
else None
|
||||
|
||||
/**
|
||||
* Used by the computer to initialize its Lua state, injecting the APIs of
|
||||
* all known drivers.
|
||||
* Gets a list of all driver APIs.
|
||||
*
|
||||
* @return the apis of all known drivers.
|
||||
*/
|
||||
private[computer] def installOn(computer: Computer) =
|
||||
(blocks ++ items).foreach(driver => {
|
||||
driver.api match {
|
||||
case None => // Nothing to do.
|
||||
case Some(code) =>
|
||||
val name = driver.getClass.getName
|
||||
try {
|
||||
computer.lua.load(code, "=" + name, "t") // ... func
|
||||
code.close()
|
||||
computer.lua.call(0, 0) // ...
|
||||
def apis = (blocks ++ items) map (driver => (driver.getClass.getSimpleName, driver.api)) collect {
|
||||
case (name, Some(code)) => (name, code)
|
||||
}
|
||||
catch {
|
||||
case e: LuaRuntimeException =>
|
||||
OpenComputers.log.warning(String.format(
|
||||
"Initialization code of driver %s threw an error: %s",
|
||||
name, e.getLuaStackTrace.mkString("", EOL, EOL)))
|
||||
case e: Throwable =>
|
||||
OpenComputers.log.warning(String.format(
|
||||
"Initialization code of driver %s threw an error: %s",
|
||||
name, e.getStackTraceString))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -7,7 +7,7 @@ import _root_.net.minecraftforge.common.ForgeDirection
|
||||
import _root_.net.minecraftforge.event.ForgeSubscribe
|
||||
import _root_.net.minecraftforge.event.world.ChunkEvent
|
||||
import java.util.logging.Level
|
||||
import li.cil.oc.OpenComputers
|
||||
import li.cil.oc.{api, OpenComputers}
|
||||
import li.cil.oc.api.network.Visibility
|
||||
import li.cil.oc.api.{network => net}
|
||||
import scala.beans.BeanProperty
|
||||
@ -26,7 +26,7 @@ import scala.collection.mutable.ArrayBuffer
|
||||
* It keeps the list of nodes as a lookup table for fast id->node resolving.
|
||||
* Note that it is possible for multiple nodes to have the same ID, though.
|
||||
*/
|
||||
class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.Node]]) extends net.Network {
|
||||
class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.Node]]) extends api.Network {
|
||||
def this(node: net.Node) = {
|
||||
this(mutable.Map({
|
||||
if (node.address < 1)
|
||||
@ -36,7 +36,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
send(new Network.ConnectMessage(node), List(node))
|
||||
}
|
||||
|
||||
nodes.foreach(_.network = this)
|
||||
nodes.foreach(_.network = Some(this))
|
||||
|
||||
def connect(nodeA: net.Node, nodeB: net.Node) = {
|
||||
val containsA = nodeMap.get(nodeA.address).exists(_.exists(_.data == nodeA))
|
||||
@ -67,7 +67,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
|
||||
private def add(oldNode: Network.Node, addedNode: net.Node) = {
|
||||
// Check if the other node is new or if we have to merge networks.
|
||||
val (newNode, sendQueue) = if (addedNode.network == null) {
|
||||
val (newNode, sendQueue) = if (addedNode.network.isEmpty) {
|
||||
val newNode = new Network.Node(addedNode)
|
||||
if (nodeMap.contains(addedNode.address) || addedNode.address < 1)
|
||||
addedNode.address = findId()
|
||||
@ -84,14 +84,14 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
nodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), List(addedNode))))
|
||||
}
|
||||
nodeMap.getOrElseUpdate(address, new ArrayBuffer[Network.Node]) += newNode
|
||||
addedNode.network = this
|
||||
addedNode.network = Some(this)
|
||||
(newNode, sendQueue)
|
||||
}
|
||||
else {
|
||||
// Queue any messages to avoid side effects from receivers.
|
||||
val sendQueue = mutable.Buffer.empty[(Network.Message, Iterable[net.Node])]
|
||||
val thisNodes = nodes.toBuffer
|
||||
val otherNetwork = addedNode.network.asInstanceOf[Network]
|
||||
val otherNetwork = addedNode.network.get.asInstanceOf[Network]
|
||||
val otherNodes = otherNetwork.nodes.toBuffer
|
||||
otherNodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), thisNodes)))
|
||||
thisNodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), otherNodes)))
|
||||
@ -114,7 +114,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
// Add nodes from other network into this network, including invalid nodes.
|
||||
otherNetwork.nodeMap.values.flatten.foreach(node => {
|
||||
nodeMap.getOrElseUpdate(node.data.address, new ArrayBuffer[Network.Node]) += node
|
||||
node.data.network = this
|
||||
node.data.network = Some(this)
|
||||
})
|
||||
|
||||
// Return the node object of the newly connected node for the next step.
|
||||
@ -152,7 +152,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
case Some(list) => list.find(_.data == node) match {
|
||||
case None => false
|
||||
case Some(entry) => {
|
||||
node.network = null
|
||||
node.network = None
|
||||
|
||||
// Removing a node may result in a net split, leaving us with multiple
|
||||
// networks. The remove function returns all resulting networks, one
|
||||
@ -306,7 +306,7 @@ object Network {
|
||||
tileEntities.
|
||||
filter(_.isInstanceOf[net.Node]).
|
||||
map(_.asInstanceOf[net.Node]).
|
||||
foreach(t => t.network.remove(t))
|
||||
foreach(t => t.network.foreach(_.remove(t)))
|
||||
}
|
||||
|
||||
private def onLoad(w: World, tileEntities: Iterable[TileEntity]) = if (!w.isRemote) {
|
||||
@ -321,12 +321,12 @@ object Network {
|
||||
getNetworkNode(world, x + side.offsetX, y + side.offsetY, z + side.offsetZ) match {
|
||||
case None => // Ignore.
|
||||
case Some(neighborNode) =>
|
||||
if (neighborNode != null && neighborNode.network != null) {
|
||||
neighborNode.network.connect(neighborNode, node)
|
||||
if (neighborNode.network.isDefined) {
|
||||
neighborNode.network.foreach(_.connect(neighborNode, node))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.network == null) new Network(node)
|
||||
if (node.network.isEmpty) new Network(node)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user