This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Laserswald's profile picture

OpenPeripherals, TE Redstone Cells, coroutines, oh my.

Started by Laserswald, 16 April 2015 - 07:38 PM
Laserswald #1
Posted 16 April 2015 - 09:38 PM
My problem is kind of insane.

I'm trying to make a base monitoring program that displays the amount of RF in total that my base has, among other things. Of course, I also want to control the system from the monitor or the keyboard. To that end, I tried using coroutines, and for the actual outputs I designed a plugin system. Here is the main file.
Spoiler

-- Based
-- A base monitoring and control daemon
os.loadAPI("lib/tcolor")

local args = { ... }

if #args ~= 1 then
  print("Usage: based <side>")
  return
end

-- Wrap the monitor to that side.
monitor = peripheral.wrap(args[1])
if monitor == nil then
  print("Side must exist.")
  return
end

-- Main function to handle events.
function event_handler()
  while true do
    event = {coroutine.yield()}
    if event[1] == "key" then
      if event[2] == keys.q then
        break
      end
    elseif event[1] == "op_tick_sync" then
      os.queueEvent(unpack(event))
    end
  end
end

-- Show the header of the program.
function display_header()
  x, y = monitor.getSize()
  for i=1,x do
    monitor.setCursorPos(i, 1)
    tcolor.write(monitor, " ", colors.blue, colors.blue)
  end
  monitor.setCursorPos(1, 1)
  tcolor.write(monitor, "Base Monitor", colors.black, colors.blue)
end

function display_info()
  while true do 
    coroutine.yield()
    monitor.clear()
    display_header()

    -- Keep from flickering.
    sleep(0.2)
  end
end

-- Plugin architecture.
local cblist = {}

function add_cb(f)
  table.insert(cblist, f)
end

function load_plugins(list)
  for c, item in pairs(list) do
    print("trying to load plugin "..item)
    os.loadAPI("bin/bdplugins/"..item)
    _G[item].plugin_setup(cblist)
  end
  print("plugins loaded")
end

-- Plugins register callbacks to the plugin manager.
function run_plugins()
  print("loading plugins")
  load_plugins(fs.list("bin/bdplugins"))
  while true do
    local event = {coroutine.yield()}
    print("Plugins running")
    for i,item in pairs(cblist) do 
      item(event, monitor)
    end
  end
end

local eventRoutine = coroutine.create(event_handler)
local displayRoutine = coroutine.create(display_info)
local pluginRoutine = coroutine.create(run_plugins)

local event = {}

while true do
  -- launch the coroutines with specific events
  coroutine.resume(eventRoutine, unpack(event))
  coroutine.resume(displayRoutine, unpack(event))
  coroutine.resume(pluginRoutine, unpack(event))

  -- Add an event to keep going
  os.queueEvent("idle")
  if coroutine.status(eventRoutine) == "dead" then
    break
  end

  event = { os.pullEvent() }
end
monitor.clear()

One of the plugins monitors the RF of one of the redstone cells in my base. Here's the code for that.
Spoiler


function plugin_setup(callbacks)   
  table.insert(callbacks, display_power)
end

function test(event, monitor)
  --print("test!")
  tcolor.write(monitor, "Testing", colors.white, colors.black)
end

function display_power(event, monitor)
  print("test")
  thermxp = "tile_thermalexpansion_cell_reinforced_name_1"
  bigr = "BigReactors-Reactor_0"
  local cell = peripheral.wrap(thermxp)
  if cell == nil then print("cell not found") end
  print("getting power")
  total = cell.getEnergyStored() -- This does not work.
  print(total)
  -- total = 5
  print("got total")
  x, y = monitor.getCursorPos()
  monitor.setCursorPos(1, y+1)
  tcolor.write(monitor, total, colors.white, colors.black)
end

When I run the main program, the thermal expansion total is never given.
The rest of the program works all the time, such as the event handling for keys and so on.

It looks like the problem is that OpenPeripherals ThermalExpansion code uses some kind of event to synchronise when it needs to check the Redstone Cell's energy level. That event gets swept up into the event pulling from the main code and never gets handled, but I am not sure.
Bomb Bloke #2
Posted 17 April 2015 - 01:05 AM
You're trying to pass events up the chain of function calls in a manner you don't really need to. Ditch the event-handling stuff in run_plugins():

-- Plugins register callbacks to the plugin manager.
function run_plugins()
  print("loading plugins")
  load_plugins(fs.list("bin/bdplugins"))
  while true do
    print("Plugins running")
    for i,item in pairs(cblist) do 
      item(monitor)
    end

    -- Maybe consider sleeping here, depending on how hard you want to flog your processor.
  end
end

… and likewise redefine your display_power function as just "function display_power(monitor)". When display_power triggers a yield (which it'll do on calling cell.getEnergyStored()), pluginRoutine will do the actual yielding, and when you resume pluginRoutine, the event data will get passed back to the point where it broke off - half way through the display_power function.

(Likewise, the sleep() call in display_info() is also triggering a yield.)

Now, when many functions yield, they're doing it because they want a specific event back. Your event handler is mostly sound, but unfortunately it's entirely ignoring requests for specific events and instead just resumes each coroutine with whatever event happens to be in the queue. This'll cause the cell.getEnergyStored() call to crash (when it gets back an event it didn't ask for, eg "idle", which really shouldn't be going into the queue in the first place - if nothing's happening then why resume anything?!), which in turn will lead to pluginRoutine becoming a dead coroutine. Your script will ignore this and keep on running without it (as you only check the status of eventRoutine).

Here's a basic example of how you might run a single co-routine:

local function myFunc()
        for i = 1, 10 do
                print(i)
                sleep(1)
        end
end

-- Initialise coroutine:
print("Starting coroutine!")

local myCoroutine = coroutine.create(myFunc)
local ok, requestedEvent = coroutine.resume(myCoroutine)

-- Run coroutine to completion:
while coroutine.status(myCoroutine) ~= "dead" do
        print("Coroutine seems ok, and is asking for a \"" .. requestedEvent .. "\" event.")

        local myEvent = {os.pullEvent(requestedEvent)}
        ok, requestedEvent = coroutine.resume(myCoroutine, unpack(myEvent))
end

-- Coroutine has finished execution.
if ok then
	print("Coroutine has completed in a natural manner.")
else
	print("Coroutine errored!: " .. requestedEvent)
end

Consider playing around with that a bit, see if you can break it (no really, see what happens if you make the coroutine crash, and see what happens if you try to resume it once it's dead). It may help to consider that this is the source for sleep() (as found in bios.lua):

-- Install globals
function sleep( nTime )
    local timer = os.startTimer( nTime or 0 )
    repeat
        local sEvent, param = os.pullEvent( "timer" )
    until param == timer
end

… and that the source for cell.getEnergyStored() will be using a similar sort of structure.

Anyway, you can rewrite your event handling loop at the bottom of your script to handle the filtering system, or you can make use of the parallel API, which basically offers you ready-made loops for the purpose. This block:

local eventRoutine = coroutine.create(event_handler)
local displayRoutine = coroutine.create(display_info)
local pluginRoutine = coroutine.create(run_plugins)

local event = {}

while true do
  -- launch the coroutines with specific events
  coroutine.resume(eventRoutine, unpack(event))
  coroutine.resume(displayRoutine, unpack(event))
  coroutine.resume(pluginRoutine, unpack(event))

  -- Add an event to keep going
  os.queueEvent("idle")
  if coroutine.status(eventRoutine) == "dead" then
    break
  end

  event = { os.pullEvent() }
end

… can be replaced with just:

parallel.waitForAny(event_handler, display_info, run_plugins)
Laserswald #3
Posted 16 June 2015 - 06:15 PM
Thanks a billion. The more I look at this, the more I can abstract my base daemon into a plugin service that shuffles data between plugins that actually process it. Sorry for the ultra late response. Again, I am really considering making both the plugin system and the base process open once I give it the old spit and polish.