ComputerCraft Forums

ComputerCraft => Ask a Pro => Topic started by: mmance on Dec 20, 2019, 12:32 am

Title: coroutines help needed. This time with plethora peripherals.
Post by: mmance on Dec 20, 2019, 12:32 am
If I call .list() or .size() on a chest in a coroutine it seems to blackhole that coroutine.  I am new to using coroutines and I do not know enough to troubleshoot.

If I call the function processChests() as a function, it works as I would expect.  Setup as a coroutine, it errors out on the first function I use chest.

I believe it errors out on line 299 where I call chest[c].list()[slot]

I have setup the peripherals as objects and then call the functions from plethora on those objects.

Thank you in advance
Code Select
function processChests ()
 
  while true do
    print("processing chests")
     
    for c=1,#chest do
      print("chest: "..c)
      for slot = 1,chest[c].size() do
        print("slot:  "..slot)
        --only look at slots that have something
        if chest[c].list()[slot] then
          print(chest[c].list()[slot].name)
          if pulverizerItems[chest[c].list()[slot].name] then
            chest[c].pushItems(pulverizer[1].address, slot, 64, 1)
            print("pulverize this")
          elseif chest[c].list()[slot].name == "thermalfoundation:material" then
            if smeltItems[chest[c].getItemMeta(slot).rawName] then
              chest[c].pushItems(furnace[1].address, slot, 64, 1)
              print("smelt this")
            end
          elseif hasDrawer(chest[c].list()[slot].name) then
            print("moving to drawer: "..chest[c].list()[slot].name)
            chest[c].pushItems(hasDrawer(chest[c].list()[slot].name), slot, 64, 1)
            print("drawer this")
          end
        end
      end
    end
   
    coroutine.yield()
   
  end
end
script
You cannot see attachments on this board..

Marc
Title: coroutines help needed. This time with plethora peripherals.
Post by: mmance on Dec 20, 2019, 01:50 am
I edited my processChests() function to

Code Select
function processChests ()
 
  while true do
    print("processing chests")

    for c=1,#chest do
      print("chest: "..c)
      for slot = 1,54 do
        if slot == 1 then
          write("slots: ")
        end
        write(" "..slot..",")
      end
     
      coroutine.yield()
     
    end
  end
end

This code has the behavior that I was anticipating for previously, but obviously does not actually do the list() function or push or pull items.

I understand that many functions will call yield so that my coroutine may yield before it got to my explicit coroutine.yield(), but if this was the case, would it not eventually get to my yield, because it is called to resume in the while loop.
Title: coroutines help needed. This time with plethora peripherals.
Post by: SquidDev on Dec 20, 2019, 09:02 am
Sorry if I end up rehashing some things you already know, but felt it was easiest to start from the top:

Every time you need to interact with the Minecraft world (such as a turtle digging a block or, as is the case here, listing items in a chest), we basically need to wait until the next Minecraft tick before we can do anything. This means that some functions (such as .list or .pushItems) have a ~0.05 second delay every time you call them.

As you end up calling list() 7 times each loop iteration, that's going to end up being about 0.35 seconds of delay for each iteration! You can avoid all of that by doing one list outside the loop - pretty similar to your second code snippet:

Code Select
for c = 1, #chest do
  local contents = chest[c].list()
  for slot, item in ipairs(contents) do -- using ipairs means that empty slots are skipped
    if pulverizerItems[item.name] then
      chest[c].pushItems(pulverizer[1].address, slot, 64, 1)
    end
    -- etc
  end
end

Note that you'll still end up with some delay each iteration, as pushItems will also pause by a tick. If you're feeling rather adventurous, you could do something with parallel.waitForAll (https://wiki.computercraft.cc/Parallel.waitForAll), which effectively allows you to run another bit of code if the first one is paused:

Code Select
for c = 1, #chest do
  local contents = chest[c].list()
  local tasks = {}
  for slot, item in ipairs(contents) do -- using ipairs means that empty slots are skipped
    table.insert(tasks, function() -- Build up a list of functions to be done in parallel
      if pulverizerItems[item.name] then
        chest[c].pushItems(pulverizer[1].address, slot, 64, 1)
      end
      -- etc
    end)
  end

  -- Run all the functions in one go - this should only take 0.05-0.10s, rather than 2 or 3.
  if #tasks > 0 then parallel.waitForAll(table.unpack(tasks)) end
end
Title: coroutines help needed. This time with plethora peripherals.
Post by: mmance on Dec 20, 2019, 08:19 pm
I changed the list() to be outside the loop, that is a much more efficient way to do it.  This still does not fix it.  The coroutine still seems to blackhole after any .list() or .size().  Even if I do just a print(chest[1].size())

Code Select
function processChests ()
 
  while true do
 
    print("processing chests")
     
    for c=1,#chest do
   
      print("chest: "..c)
      print(chest[1].size())  -- hangs here
     
      local contents = chest[c].list()
       
      for slot, item in ipairs(contents) do
     
        print("slot:  "..slot)
       
        if pulverizerItems[item.name] then
           
          chest[c].pushItems(pulverizer[1].address, slot, 64, 1)
           
          print("pulverize this")
         
        elseif item.name == "thermalfoundation:material" then
       
          if smeltItems[item.rawName] then
       
            chest[c].pushItems(furnace[1].address, slot, 64, 1)
         
            print("smelt this")
           
          end
         
        elseif hasDrawer(item.name) then
       
          print("moving to drawer: "..item.name)
       
          chest[c].pushItems(hasDrawer(item.name), slot, 64, 1)
       
          print("drawer this")
         
        end
       
      end
     
    end
   
    coroutine.yield()
   
  end
 
end



on a side note, I just wanted to thank you for being so awesome.
Title: coroutines help needed. This time with plethora peripherals.
Post by: mmance on Dec 20, 2019, 08:22 pm
I say blackhole, but what I mean is that I can look at coroutine.status and it says suspended, when I do a coroutine.resume, it replies true, but does not seem to actually resume.

Did you see any reason why it shouldn't work? albeit slow and inefficient?
Title: coroutines help needed. This time with plethora peripherals.
Post by: SquidDev on Dec 20, 2019, 08:38 pm
Quote from: mmance on Dec 20, 2019, 08:22 pmI say blackhole, but what I mean is that I can look at coroutine.status and it says suspended, when I do a coroutine.resume, it replies true, but does not seem to actually resume.
Oooooh, are you running this code within your own coroutine manager? Can you post the code of that too?
Title: coroutines help needed. This time with plethora peripherals.
Post by: mmance on Dec 20, 2019, 09:07 pm
I am just using the coroutine api.  The whole code is posted in the first post as an attachment.
Title: coroutines help needed. This time with plethora peripherals.
Post by: SquidDev on Dec 20, 2019, 09:22 pm
I was just curious where you were calling coroutine.resume? You shouldn't normally need to do that.
Title: coroutines help needed. This time with plethora peripherals.
Post by: mmance on Dec 20, 2019, 09:27 pm
I am calling it in my main program loop.  I expected that each time it was called, it would process a single chest and then yield to main.

It could be a fault in my understanding of how to use them.
Title: coroutines help needed. This time with plethora peripherals.
Post by: Wojbie on Dec 20, 2019, 09:27 pm
Ohh i see what you are doing. You don't seem to understand how coroutines work or just don't realize that list() causes yield.

Every time you call list() it internally calls coroutine.yield("plethora_task") and is awaiting the event that signified the said task is complete. In your case you are discarding returns from coroutine.resume and using sleep() later which discards all events while sleeping. In effect you are discarding said "plethora_task" event in the sleep and resuming list with empty event so list() never know when it can return.

I would suggest looking how monitor and multishell programs work if you want to understand coroutines.
Title: coroutines help needed. This time with plethora peripherals.
Post by: mmance on Dec 20, 2019, 09:31 pm
thank you for that, that gives me something to go on.
Title: coroutines help needed. This time with plethora peripherals.
Post by: Wojbie on Dec 20, 2019, 09:36 pm
You approach with using just resume and yeld without event would have worked perfectly if you didn't need to call any functions that interact with world (like turtle movement/inspect , most of plethora chest interactions ect). IF it was function without any function that yeld internally (like pure calculation) it would work without issues way you wrote it. Sadly you are trying to interact with world so you do need to react to world events.
Title: coroutines help needed. This time with plethora peripherals.
Post by: mmance on Dec 20, 2019, 09:40 pm
that helps a lot.  I am so new to this that I was having trouble on where to start.
Title: coroutines help needed. This time with plethora peripherals.
Post by: SquidDev on Dec 20, 2019, 10:04 pm
Oh, sorry - my brain entirely missed the download link in the OP. In addition to what Wojbie says, you need to be careful, as clearMachineOutput and processStrongbox also will yield, which means they throw away the plethora_task events that processChests needs.

The easiest thing to do here is probably just run the three in parallel:

Code Select
-- Helper function which runs fn forever.
local function forever(fn)
  return function()
    while true do
      fn()
      sleep(2)
    end
  end
end

-- Run all three in parallel
parallel.waitForAll(processChests, forever(clearMachineOutput), forever(processStrongbox))