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

Clever way to solve "commands.async: Task limit exeeded"?

Started by Kaikaku, 25 April 2015 - 10:06 AM
Kaikaku #1
Posted 25 April 2015 - 12:06 PM
Hi!
I'm working on a command computer based map/game. I'm using lots of commands.async.setBlock() commands.
While developing and testing, I simply wait at certain points using read().
That's fine for now and I could check out good sleep durations instead of hitting enter,
but as this sould run also smoothly for other players I'm courious if there are clever ways to solve this?

Generally not using async is no option, as it would take ages to set everything up.
Maybe I can somehow get the number of open tasks, so I can sleep depending on this info?
InDieTasten #2
Posted 25 April 2015 - 12:25 PM
At least you know the limit by that info from the wiki
  • ComputerCraft enforces a limit of 1000 commands per server tick on Command Computers. Consider yielding manually every so often (eg, sleeping for a second) when making large amounts of commands.execAsync calls.
With the info here you can manage your async commands, and automatically sleep for a sec when you reach your limit.

I guess the rest is pretty easy to figure out. if not, feel free to ask what part is still confusing to you :)/>
InDieTasten #3
Posted 25 April 2015 - 12:35 PM
Another way would be to have some kind of callcounter to commands.async.setBlock() which will automatically yield a couple ticks, after the internal counter reaches the limit

Yet another way would be(with this amount of blocks) reducing the amount of calls by cloning similar block patterns as a region, and thereby reducing the amount of calls in total, which will be most optimal I guess, since it reduces the api's load from mod to server also.
Edited on 25 April 2015 - 10:41 AM
Bomb Bloke #4
Posted 25 April 2015 - 12:35 PM
You can't really 100% solve this at present. Having tons of commands.async() calls resolving, for some reason, causes the computer to start missing events - of all kinds - at random. If you try implementing sleeps you'll find that they randomly fail to resolve and simply cause the computer to hang forever (as their timer event never comes back).

That said, take a look at the source for my WorldPorter script, specifically, the doBuild function (which operates in the same manner as InDieTasten suggests above). I realise that my code tends to make for difficult reading, but it's mostly immune to this event-dropping (it can survive over a couple of hundred dropped events), and the odds of it fully hanging the computer before completion are low enough that I haven't seen that happen yet.

I haven't figured out how to stabilise skyTerm yet, but if the event-dropping bug gets fixed, that one'd also work as-is.
Kaikaku #5
Posted 25 April 2015 - 02:45 PM

Yet another way would be(with this amount of blocks) reducing the amount of calls by cloning similar block patterns as a region, and thereby reducing the amount of calls in total, which will be most optimal I guess, since it reduces the api's load from mod to server also.
Clon as well as fill is introduced in MC1.8, so this is sadly no option yet…

Another way would be to have some kind of callcounter to commands.async.setBlock() which will automatically yield a couple ticks, after the internal counter reaches the limit
Thanks, I'll try this out!

You can't really 100% solve this at present. Having tons of commands.async() calls resolving, for some reason, causes the computer to start missing events - of all kinds - at random. If you try implementing sleeps you'll find that they randomly fail to resolve and simply cause the computer to hang forever (as their timer event never comes back).

That said, take a look at the source for my WorldPorter script, specifically, the doBuild function (which operates in the same manner as InDieTasten suggests above). I realise that my code tends to make for difficult reading, but it's mostly immune to this event-dropping (it can survive over a couple of hundred dropped events), and the odds of it fully hanging the computer before completion are low enough that I haven't seen that happen yet.
Indeed, at least for me not easy to read ;)/>, but still very interessting. I haven't played around with parallel so far and have only partly understood it now.
Usually I would just try it out, but with this I'd like to make sure, that I'm not completely wrong.
After looking at your code, I would try some thing like this:


-- variables
local maxCommands=250
local curCommands=0
local commandComputerCommand=""

-- functions
local function pullTaskComplete()
  while true do
	os.pullEvent("task_complete")
	curCommands = curCommands - 1
  end
end

local function doOneCommand()
  while curCommands >= maxCommands do os.pullEvent("task_complete") end
  commands.execAsync(commandComputerCommand)
  curCommands = curCommands + 1
end

-- main
while true do
  -- building program stuff, incl.
  -- commandComputerCommand="..."
  parallel.waitForAny(pullTaskComplete,doOneCommand)
end

I guess I don't have to put the whole functions in the parallel call?
Do I need any further pullEvents?
Bomb Bloke #6
Posted 25 April 2015 - 03:26 PM
That looks like it'll work as-is, so long as you don't pull any events within that "while" loop at the bottom of the script (for example, by calling read(), or by attempting to receive a rednet message).

If you need to pull other events, then you'd re-organise like this:

Spoiler
-- variables
local maxCommands=250
local curCommands=0
local commandComputerCommand=""

-- functions
local function pullTaskComplete()
  while true do
        os.pullEvent("task_complete")
        curCommands = curCommands - 1
  end
end

local function doWork()
  while true do
    -- building program stuff, incl.
    -- commandComputerCommand="..."

    while curCommands >= maxCommands do os.pullEvent("task_complete") end
    commands.execAsync(commandComputerCommand)
    curCommands = curCommands + 1
  end
end

-- main
parallel.waitForAny(pullTaskComplete,doWork)

Or, for added flexibility (allowing you to perform a command from multiple points within your main work loop, should you so choose):

Spoiler
-- variables
local maxCommands=250
local curCommands=0
local commandComputerCommand=""

-- functions
local function doOneCommand(commandComputerCommand)
  while curCommands >= maxCommands do os.pullEvent("task_complete") end
  commands.execAsync(commandComputerCommand)
  curCommands = curCommands + 1
end

local function pullTaskComplete()
  while true do
        os.pullEvent("task_complete")
        curCommands = curCommands - 1
  end
end

local function doWork()
  while true do
    -- building program stuff, incl.
    -- commandComputerCommand="..."

    doOneCommand(commandComputerCommand)
  end
end

-- main
parallel.waitForAny(pullTaskComplete,doWork)

Edit: Bear in mind commands.getBlockInfo will also generate and pull a "task_complete" event, so if you're calling that, you'll also need to increase curCommands so as not to confuse pullTaskComplete(). I'm not sure about commands.getBlockPosition off the top of my head, but by modifying the myFunc() function in the coroutine example here, you can find out what - if any - events any function attempts to pull. The listed example, for instance, demonstrates that sleep() tries to pull timer events.
Edited on 25 April 2015 - 01:34 PM
Kaikaku #7
Posted 25 April 2015 - 04:08 PM
That looks like it'll work as-is, so long as you don't pull any events within that "while" loop at the bottom of the script (for example, by calling read(), or by attempting to receive a rednet message).

If you need to pull other events, then you'd re-organise like this:

Or, for added flexibility (allowing you to perform a command from multiple points within your main work loop, should you so choose):

Edit: Bear in mind commands.getBlockInfo will also generate and pull a "task_complete" event, so if you're calling that, you'll also need to increase curCommands so as not to confuse pullTaskComplete(). I'm not sure about commands.getBlockPosition off the top of my head, but by modifying the myFunc() function in the coroutine example here, you can find out what - if any - events any function attempts to pull. The listed example, for instance, demonstrates that sleep() tries to pull timer events.

Aha, think I now understand better why you did your WorldPorter build function the way it is.
It's very likely, that without your help I'd would have coded s.th. that is either not effective or not working on the long-run…

Great, many thank! :D/>