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

Creating an order queue...

Started by Kong_Ming, 06 January 2013 - 05:55 AM
Kong_Ming #1
Posted 06 January 2013 - 06:55 AM
So here's what I'm trying to do…

I've created a fairly complex machine that's designed to produce many of the more labor-intensive items in Tekkit (MFSU, HV-Solar array, etc.). I want to set the whole thing up to be controlled by a user-terminal that can request a specific item in a certain quantity. Doing so delivers the item in that quantity to the user from a stockpile, and then runs the machine to produce the necessary replacements for the stockpile. This way the user doesn't need to wait for the item to be produced, but the stockpile is always kept full for future requests. The machine is modular and each individual component that needs to be crafted is built by a module which is in turn controlled by it's own computer that receives requests from either the user or other control computers in a sort of cascade effect.

For example, if I order 1 generator from the user-terminal, it sends a request for 1 generator to the generator-computer. The generator-computer sends a redstone signal to the generator-module which produces 1 generator. It also sends a request to the IronFurnace-computer and the REBattery-computer for 1 of each of those components to replace the ones consumed by the generator-module. The IronFurnace-computer sends a redstone signal to the IronFurnace-module which produces one of those, and the REBattery-computer does the same to produce another battery from the REBattery-module. Also, the REBattery-computer sends request to the CopperWire-computer to produce replacement copper wire, etc.

The issue I already see coming is that some of the more expensive items take up to several minutes to build replacements for. If I need to make several requests for the same part, I have to space my redstone signals by as much as .5 seconds to ensure the machine can react to each signal properly. The computers I have controlling the various modules of my machine and requesting the replacement parts would be too busy sending out measured redstone signals to listen for any new requests made by the user-terminal or even by other control computers. I could simply force the user to wait until the system is free again, but I'd rather not. I've thought that I could perhaps introduce another intermediate computer that handles requests from the user by creating a queue, and waits until whatever machine component is required has finished its current order before sending any queued orders. Am I over-thinking this? Is there a way to have a computer listen for a rednet event while it is executing other code? Or do I need a queue computer to be dedicated to this task? I eagerly await the ingenuity of others.

I can also explain the machine in greater detail if necessary.
GopherAtl #2
Posted 06 January 2013 - 07:15 AM
no reason to use a separate computer for the queue. In general, you get a lot of flexibility by designing your programs around an os.pullEvent() loop, as described in detail in this thread. If you've got an existing program of some complexity you don't want to completely rewrite to use a pullEvent loop, you can write the new code as a separate function and run it along side the current program using the parallel api, as described in this thread.
Kong_Ming #3
Posted 06 January 2013 - 03:41 PM
Ah, it looks like the parallel API may be the piece I was missing. However, I don't seem to be doing it properly. When executing my code, the computer got hung-up and not even a CTRL-T could get me out of it. I had to actually restart the entire server to unfreeze the program. What did I do!?


function buildstuff()
while true do
  if quantity > 0 then
   print(tostring(quantity))
   sleep(.5)
   quantity = quantity - 1
  end
end

function listening()
  while true do
	event, order = os.pullEvent("char")
	quantity = quantity + tonumber(order)
  end
end

quantity = 0
while true do
  parallel.waitForAny(buildstuff, listening)
end

This was just a test program to make sure the theory was sound (clearly it isn't). The idea was that one function waits for the global variable "quantity" to increase above 0, which then makes it perform a cyclical function of work (in this case printing "quantity"), while decrementing "quantity" back to zero. The other parallel function would wait for a keystroke and add that numerical value to "quantity" thus giving the first function more work to do. Something has clearly gone wrong however, as nothing prints at all and the program hangs. Is the global variable a bad idea? I've heard many warnings against them in my limited experience with coding, but this seemed like just the right scenario for using one. Thanks for the help!
Orwell #4
Posted 06 January 2013 - 03:54 PM
The function buildstuff() never yields when quantity equals zero. When code in CC doesn't yield for 10 seconds, it will be interrupted. Normally, this should happen in a more elegant way, I'm not sure why it happens so agressive on your server. :P/>/>

So, the solution: put this in the while loop of your buildstuff function, but not in the if statement:
coroutine.yield()
Kong_Ming #5
Posted 06 January 2013 - 05:42 PM
I see, that makes sense. I've added a coroutine.yield() in the following fashion which seems to have helped. I still have a problem though. Here's the new code:


function buildstuff()
  while true do
	if quantity > 0
	 print(tostring(quantity))
	 quantity = quantity - 1
	else
	 coroutine.yield()
	end
  end
end

function listening()
  while true do
	event, order = os.pullEvent("char")
	quantity = quantity + tonumber(order)
  end
end


quantity = 0
while true do
  parallel.waitForAny(buildstuff, listening)
end

This addition allows for a yield whenever "quantity" is not greater than 0. Which does improve things a bit. However, I've still been unable to solve the problem in my OP unfortunately. Now the program responds after each keystroke, doing a countdown from whatever number was pressed prior to the current keystroke. All this is fine and well, the issue is that the program is sitting and waiting for the following keystroke event rather than simply proceeding with the countdown in buildstuff(). It still seems like I'd have to have two computers to truly do these tasks simultaneously as the parallel API seems to be more of a back-and-forth process rather than true simultaneity. Of course I could be completely wrong and just implementing the whole thing incorrectly. Hmmmm……

Is there any way to completely interrupt a process when an event takes place, execute a different function, and then resume the original process where it left off?
Orwell #6
Posted 06 January 2013 - 10:51 PM
Is there any way to completely interrupt a process when an event takes place, execute a different function, and then resume the original process where it left off?
That's practically what parallel does.. I'd start by putting 'coroutine.yield()' outside of the if statements, as I said, a*n not in an else clause. You want it to yield on every iteration. Yielding is when your program flow switches to another point where it first yielded to then switch back on the next yield. So in your case, it will only go listen for character input when quantity is zero.

Fix that first and let us know what happens then. We can think further from there on.
Kong_Ming #7
Posted 07 January 2013 - 05:44 AM
I had tried it outside the if clause as well, but it seems the real problem is how to yield during the os.pullEvent. For now the program is stuck listening for the event even though "quantity" has a value > 0.

I guess what I ultimately want to happen if I'm using parallel is to do the print command, yield for .5 seconds to listen for os.pullEvent(), and then regardless of whether an event occurs or not, yield back to print again after .5 seconds, so that the program is always proceeding with the decrement of "quantity" unless it = 0, but still spending 99% of it's time listening for the event.

Thanks for the help, things are getting clearer. Hopefully I can figure out how to make the above occur. I think the main thing I need to figure out, is how to yield back to buildstuff() if listening() hasn't heard an event after a short while.

Does the computer wait for an event because os.eventPull sits and waits the same way read() does, or is it stuck because of the while loop it's contained in? Clearly I need to better understand os.pullEvent().
Orwell #8
Posted 07 January 2013 - 05:47 AM
I would probably take a different approach, but I might have a quick fix. I see that Lyqyd is reading this and he's not a huge fan of parallel so he'll probably point you in a different direction. :P/>

The quick fix is to artificially create an event after 0.5 seconds;

function buildstuff()
while true do
  if quantity > 0 then
   print(tostring(quantity))
   quantity = quantity - 1
  end
  coroutine.yield()
end
function listening()
  while true do
		os.startTimer(0.5)
		event, order = os.pullEvent()
		if event == "char" then
		  quantity = quantity + tonumber(order)
		end
  end
end
quantity = 0
while true do
  parallel.waitForAny(buildstuff, listening)
end
Lyqyd #9
Posted 07 January 2013 - 05:49 AM
Not sure why Orwell suggested a raw yield. This may do what you're looking for. Also, parallel isn't strictly necessary for cases like this; this can be handled with timer and key events in a single loop, if desired.


function buildstuff()
  if quantity > 0 then
    print(quantity)
    quantity = quantity - 1
  end
  sleep(0.5)
end

Edit: the single-loop case isn't terribly difficult.


local quantity = 0
local countTimer = os.startTimer(0.5)

while true do
  e, p1 = os.pullEvent()
  if e == "char" then
    quantity = quantity + (tonumber(p1) or 0)
  elseif e == "timer" and p1 == countTimer then
    if quantity > 0 then
      print(quantity)
      quantity = quantity - 1
    end
    countTimer = os.startTimer(0.5)
  end
end
Kong_Ming #10
Posted 07 January 2013 - 07:01 AM
I actually just figured out that a timer was the way to go myself when I saw your post. Here's the the less sophisticated code I came up with. There are some extra print commands going on just so I could see how things were working. I was having trouble at first as timer seemed to fire all the time, regardless of how much time had passed, until i realized I was setting multiple timers and needed to control them with an if statement. Defining the timer to a variable hadn't occurred to me.


funtion buildstuff()
  if quantity > 0 then
	print(tonumber(quantity))
	quantity = quantity - 1
  end
end

quantity = 0
x = 0
while true do
  if x == 0 then
	os.startTimer(3.0)
  end
  x = 1
  event, order = os.pullEvent()
  print(event)
  if event == "timer" then
	buildstuff()
	x = 0
  elseif event == "char" and order == "b" then
	shell.exit()
  elseif event == "char" and tonumber(order) then
	quantity = quantity + tonumber(order)
	print("Added ", order)
  end
end

This code does exactly what I wanted except for one thing. The shell.exit() doesn't work, so I have to physically break the computer terminal to get the program to stop. Any idea why it won't happen? Also, thanks again for the feedback, you guys rock. This mod is so much fun in conjunction with redpower components!
GopherAtl #11
Posted 07 January 2013 - 07:04 AM
shell.exit() doesn't work in that context. In your case you can just replace it with "return"
Kong_Ming #12
Posted 07 January 2013 - 08:21 AM
Ok, I'm unfamiliar with "return". Is it a function? Or an operation? What does it do exactly? Is it similar to break? Will it remove me from the while loop? Maybe I should stop asking 20 questions at a time….
Lyqyd #13
Posted 07 January 2013 - 08:23 AM
It returns any values you give it (like return 5) and exits from the current function. Since your whole program is run as a function, if you're not in a function inside your program, it'll exit from the program. However, you'll have to move its condition to be at the end of the block. Can't have an elseif after it, IIRC.
Kong_Ming #14
Posted 07 January 2013 - 08:58 AM
Ah, that makes sense. Perfect! I think I have all the components I need to run my machine. Time to start building! Thanks for all the help guys! I'll post a vid if I get it all running correctly.
ChunLing #15
Posted 07 January 2013 - 09:23 PM
You can use return anywhere that it will be the last statement executed in a chunk if it is reached (of course, it will be the last statement executed anyway because it's return, but where it would be the last statement of the chunk executed even if it wasn't return). That means that it can indeed be used before an elseif or an else, and will work properly.
Lyqyd #16
Posted 08 January 2013 - 04:06 AM
I don't disagree with having it immediately before an else, but I don't think I've seen a return working just before an elseif.

The reference manual:
http://www.lua.org/manual/5.1/manual.html#2.4.4
And the PIL docs:
http://www.lua.org/pil/4.4.html
Would seem to agree with me. I'd love to see working code that disagrees, though!
ChunLing #17
Posted 08 January 2013 - 07:02 AM
Yeah, just put a simple function into lua, like:
function rtrn(n)
if n < 1 then
  return "less than one"
elseif n < 5 then
  return "less than five"
else
  return "five or more"
end end
Hah, fear my indenting laziness, but when I put this in lua, it was all on one line, so I don't care.

Anyhoo, all the returns worked fine for me when I tried it. Like the document says,
The return and break statements can only be written as the last statement of a block
In this case, the block is defined as:
stat ::= if exp then block {elseif exp then block} [else block] end
So an elseif ends an if then block just as an else would.

Of course, that means that you can also put return/break in places that don't make a lot of sense, like just before the end of a loop (so it only executes once before returning). Oh well. Lua generally allows you do anything that makes sense, it doesn't always prevent doing things that make no sense.
Lyqyd #18
Posted 08 January 2013 - 07:10 AM
Okay, thanks for verifying! It didn't entirely make sense that it wouldn't work before an elseif, but I've seen weirder quirks before. It'd be handy if the PIL document mentioned elseif as well, since it mentions everything else it can come immediately before.
ChunLing #19
Posted 08 January 2013 - 07:41 AM
Heh, it makes no sense that you're allowed to put it at the end of a loop's block rather than requiring it be inside of a conditional, but such are the vagaries of defining a syntax.
GopherAtl #20
Posted 08 January 2013 - 01:24 PM
according to the lua spec, return can go at the end of any block. Including anonymous "do…end" loops, which I didn't know you could even do in lua until reading the language specs for unrelated reasons earlier. Why you would use them I'm still not clear on.


do
  return
end
print("unreachable code!")

perfectly valid, and perfectly pointless.
ChunLing #21
Posted 08 January 2013 - 04:10 PM
Well, they actually suggest that one in the programmer's guide as a way to help debug functions. But I frankly agree with your assessment.
NeverCast #22
Posted 09 January 2013 - 08:05 AM
I was NOT aware that a return had to be at the end of a block, with all the code I've written I must have struck lucky, that said you would NEVER ( in my experience ) need to return something in the middle of a block, so I probably never did so and never encountered this issue.
Kong_Ming #23
Posted 09 January 2013 - 11:04 AM
Well, got it all working! It's still a work in process, but now it's just a matter of expanding on something that's already fully functional. Thanks again for the help! Here's the video:

[media]http://www.youtube.com/watch?v=EZVPe1cSDfU[/media]

And this is the code I used for the control computers. Obviously each computer sends a signal to a different color wire to control each specific machine module. I'm sure it could be prettier, but it works. I considered only using one computer to control all 16 modules, but I wanted to keep the rednet message system simple and have the more robust feedback from multiple displays. As it is, each time a computer uses a component, it sends a message to the computer that controls that component to create a replacement, which sends other requests for any components it consumes and so on. So ordering a more complex item creates a cascade effect as I show in the second example in the video.


function buildstuff()
  if redstone.testBundledInput("back",colors.red)
	redstone.setBundledOutput("back",0)
  elseif quantity > 0 then
	quantity = quantity - 1
	print("Building. ", quantity, " remaining in queue...")
	redstone.setBundledOutput("back",colors.red)
  end
end

rednet.open("left")
quantity = 0
x = 0
while true do
  if x == 0 then
	os.startTimer(0.5)
  end
  x = 1
  event, compID, order = os.pullEvent()
  if event == "timer" then
	x = 0
	buildstuff()
  elseif event == "rednet_message" and tonumber(order) then
	quantity = quantity + tonumber(order)
	print("Added ",order)
  elseif event == "char" and compID == "b" then
	print("Exiting...")
	return
  end
end
ChunLing #24
Posted 09 January 2013 - 06:04 PM
Looks very pretty.
GopherAtl #25
Posted 10 January 2013 - 03:46 AM
I was NOT aware that a return had to be at the end of a block, with all the code I've written I must have struck lucky, that said you would NEVER ( in my experience ) need to return something in the middle of a block, so I probably never did so and never encountered this issue.

If you think about it a bit, you'll realise it's not luck. Return or break anywhere but the end of a block makes no logical sense in general, ex:


function f()
  print("1")
  return
  print("2") --this makes no sense, obviously it would never run
end