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

Regarding Timing

Started by Bomb Bloke, 26 May 2013 - 04:26 AM
Bomb Bloke #1
Posted 26 May 2013 - 06:26 AM
I'm familiar with a few languages, and though Lua is relatively new to me, I believe I have a good "head start" and a serviceable grasp of it. The closest parallel I've dealt with would probably be BASIC.

I've created a turtle to process stuff for me. I've built a number of alvearies (beehives), and a BuildCraft pipe system deposits the honey combs they produce (which are of various types) into a collection of barrels. The turtle's job is to collect resources from these barrels and take them to machines for processing. Initially I had it take them to a centrifuge, wait around (sleep) until the stack was complete, then dump the results back into the same sorting system the alvearies feed. I added in a few other such jobs over time (eg, running dust through a furnace).

... Tables and variable declarations, etc...

function processComb()
... Check with a rednet inventory server to see if the current barrel is worth dealing with, ...
... collect combs, drag them to the centrifuge, drop them in...
   sleep(math.floor(turtle.getItemCount(1) * 5.25) + 8)
... collect the results, drop them off, increment the barrel counter...
end

... More functions for other jobs, movement, etc...

... Init actions; rednet handshakes, GPS stuff, etc...

-- Main work loop
while true do
... Make sure my generators (and the turtle) have fuel...
	cookStuff()
	compressStuff()
	processComb()
end

Because it now also does these other jobs, I wanted to remove the "waiting around" bit and simply have it move on to the next task directly after feeding each machine. I looked into timers as a way of doing this, but os.startTimer() appeared completely unsuitable as the turtle must be sitting around doing nothing in order to catch the resulting event generated.

So I decided to go with os.clock() (per the below pseudo-code), but I was a little apprehensive about doing so. As it tracks the number of seconds since the turtle booted I figured this'd involve dealing with some very large numbers.

... Tables and variable declarations, etc...

function processComb()
... Check with a rednet inventory server to see if the current barrel is worth dealing with, ...
... collect combs, drag them to the centrifuge...
   centritimer = os.clock() + math.floor(turtle.getItemCount(1) * 5.25) + 8
... Drop the current set of combs, collect the results from the last batch of combs ...
... that were processed, drop them off, increment the barrel counter...
end

... More functions for other jobs, movement, etc...

... Init actions; rednet handshakes, GPS stuff, etc...

-- Main work loop
while true do
... Make sure my generators (and the turtle) have fuel...
	if os.clock() > cooktimer then cookStuff() end
	if os.clock() > compresstimer then compressStuff() end
	if os.clock() > centritimer then processComb() end
end

So question number one: Is there a more elegant way of doing this then tracking numbers that increase forever?

Anyway, I figured that as it's dealing with double precision floats it'd probably be good for at least a few months or so before I had to worry about overflows or any such thing (longer then the server's average uptime at least), and hence I decided to give it a shot. Sure enough, the turtle merrily floated around my base and so I logged off for the night. Only to come back in the morning to find my server (which runs on a local computer in my LAN and had been going about a week) had completely stalled - all logging stopped about two hours after I logged off. Prior to that there were some "ConcurrentModificationException"s logged but they appear all the time for me anyways.

I tried to close the server program normally, which triggered a little bit of info in the output terminal:

2013-05-25 08:32:10 [INFO] [Minecraft] Stopping server
2013-05-25 08:32:10 [INFO] [Minecraft] Saving players
2013-05-25 08:32:10 [INFO] [Minecraft] Saving worlds
2013-05-25 08:32:10 [INFO] [Minecraft] Saving chunks for level 'world'/Overworld
2013-05-25 08:32:11 [INFO] [STDERR] java.net.SocketException: socket closed
2013-05-25 08:32:11 [INFO] [STDERR]	 at java.net.TwoStacksPlainSocketImpl.socketAccept(Native Method)
2013-05-25 08:32:11 [INFO] [STDERR]	 at java.net.AbstractPlainSocketImpl.accept(Unknown Source)
2013-05-25 08:32:11 [INFO] [STDERR]	 at java.net.PlainSocketImpl.accept(Unknown Source)
2013-05-25 08:32:11 [INFO] [STDERR]	 at java.net.ServerSocket.implAccept(Unknown Source)
2013-05-25 08:32:11 [INFO] [STDERR]	 at java.net.ServerSocket.accept(Unknown Source)
2013-05-25 08:32:11 [INFO] [STDERR]	 at hu.run(ServerListenThread.java:81)
2013-05-25 08:32:11 [INFO] [STDOUT] Closing listening thread

And then it just hung there until I ended the task.

I restarted it to find the turtle (a wireless crafty model) had reverted to a regular turtle with no label - not surprising, it'd done the same thing when the server had crashed quite some time before (for reasons entirely unrelated), so I left the server running while at work and then on my return I rebuilt the turtle and set it to run overnight again. Again, the server completely stalled some time after log out, this time without the "ConcurrentModificationException"s to clue me in as to when (nothing was logged since I logged out, though trying to stop the server resulted in the exact same extra messages as the previous day, the same need to end the task, and the same need to rebuild the turtle).

So, barring some big coincedence, my turtle is killing my server somehow. It'd take a week of testing to say for sure how (as it runs for hours without issue and nothing gets recorded when it DOES die), so whether or not my use of the os.clock() function has anything to do with it is purely conjecture at this point.

So question number two: Has ComputerCraft been known to trigger server hangs, or are there any known issues with os.clock()?
Engineer #2
Posted 26 May 2013 - 07:14 AM
Well, I would make something like this (going to explain it afterwards):

--# I would make it rednet based
rednet.open("right")

while true do
   local _, id, message = os.pullEvent("rednet_message") --# Wait for a rednet message
   if id == <ID OF SERVER> then
      if message == "task" then
         --# executions of the task
         rednet.send(id, "Done with " .. message) --# Just to make a handshake
      elseif message == "task1" then
         --# same as previous
      end
   end
end

Then for your server you would have:

rednet.open("side")

--# since you have a routine of doing something, you can have something like:

rednet.send( <ID OF TURTLE>, "task" )
while true do
  local id, message = rednet.receive() --# wait forever until you receive a message
  if id == <ID OF TURTLE> then
     --# execute additional server side stuff
     break
  end
end
--# etc

As you can see, this way only the turtle has to execute certain functions or programs. But to give you an answer to your first question: You can simply use os.startTimer. This function returns you the id of the timer, so you can run multiple:

local firstTimer = os.startTimer(<DELAY>)
local secTimer = os.startTimer(<DELAY>)
local thirdTimer = os.startTimer(<DELAY>)

print("ID's: 1:"..firstTimer.." 2:"..secTimer.." 3:"..thirdTimer)
while true do
  local _, tID = os.pullEvent("timer")
  if tID == firstTimer then
     --# send command
     --#restart timer
     firstTimer = os.startTimer(<SAME DELAY>)
  --# etc
  end
end

Timers are the way to go, or at least I think it is.
If you have questions on the code, please ask them!
Bomb Bloke #3
Posted 26 May 2013 - 08:21 AM
Thanks for your reply. I guess I'll answer you backwards, too. :)/>

At least as far as I understand it, that last code block won't work: if the second or third timers fire while the turtle is performing the first task, then it has no way of catching the resultant events. It has to be sitting around waiting for those timers in order to catch them, and the whole point is to avoid sitting around waiting: the turtle has enough work to do that it's always moving. It may be anywhere up to a couple of minutes after a timer completes until the turtle is even ready to check in on that timer, depending on its current job(s) (say all three timers fire before it can complete one job, say. How will it know?).

To test this, I went up to a computer I've set up purely to run code snippets on, typed "lua", and then entered the following four lines:

print(os.startTimer(60))
(Outputs eg 4)
print(os.startTimer(1))
(Outputs eg 5)
event, timerid = os.pullEvent()
(Sits there for a bit over half a minute before returning to the command line)
print(timerid)
(Outputs eg 4, or whatever the first timer ID was)

That is to say, because it takes me longer to type the third line then it takes for the second line's event to fire, I cannot seem to pull timer ID 5. Nor do I see any way to check that it's been fired, short of recording and comparing values from os.clock() - the use of which makes the entire event system pointless for timing, unless the program does nothing between events (that is to say, it's only feasible if you're planning on running just one single timer at a time).

The turtle has the same problem as me typing into the command line, see. It's busy flying around, moving, grabbing, placing and crafting - it can't constantly check the timers, certainly not at the rate it'd need to in order to catch them before they each fire, but it has to if it wants to make use of them.

Though, again, I'm relatively new to Lua, so I suspect that I'm missing something and there's a way of watching the timers while at the same time doing something completely different - or pulling them some arbitrary amount of time after they fire?

As for the first two code blocks, they seem to have a similar issue to the last: Odds are if the server sends a message to the turtle it won't receive it because it's busy working. I could work around that by having the server spam rednet messages until the turtle happens to catch one, but it seems to me it'd be better done, well, backwards: Have the server fire the timers, and sit and wait for either one of them to fire (in which case it toggles a value in a table of booleans) or for the turtle to ask it if a job happens to be ready for it (in which case it reports according to the aforementioned table). Because the server doesn't have much to do anyways it's in a much better position to watch timers.

The problem with this is that a timer event could still fire while it's talking to the turtle instead of watching the event queue, in which case it seems I'm back to square one: Once a timer event has been missed, I've no way of knowing about it short of referring to os.clock(), which makes the use of the timer event system pointless in the first place. :(/>
Engineer #4
Posted 26 May 2013 - 08:49 AM
Well, for your first code block:
(Take longer delays when testing)

That is basically the plan, just have the server do the delays and sending commands. But the thing is, not everything is programmable, you must make sure that your turtle is standing by already before your server can receive the message.

That is something, you have to time, and make a reasonable delay between delays. If you really want to catch timers, and do stuff simultaneously, you can use coroutines. But that is rather complicated, and if you apply some logic to it, you dont coroutines.

A short conclusion:
- A server with reasonable delay between messages
- A turtle who executes commands and is standing by in time before a message gets sent.
Bomb Bloke #5
Posted 26 May 2013 - 10:40 AM
Sorry, that last line isn't fully visible - could you please list out the rest of it?

What's the difference between "standing by" for a rednet message from a server, and "standing by" for an event from a timer? Is it possible to pull rednet messages some time after they've been sent, even if the turtle wasn't sitting around "doing nothing" at that time?
Engineer #6
Posted 26 May 2013 - 11:10 AM
Where the lua> <code> is, just prints the current event with the parameters. Im think you dont understand os.pullEvent, so here is a little explanation:

With os.pullEvent() you wait for a event to happen (click for full list). If you put in a filter, it just waits for that specific event, nothing else. If you give it no filter, then it triggers on every event. Here a quick example:

while true do -- infinite loo
   local event = {os.pullEvent()} -- Put everything that os.pullEvent returns in a table
   print( table.concat(event,", ") )
end
This prints every event that happens, but when you do os.pullEvent("check_eventlist") it will not trigger until an event "check_eventlist" comes.

That should hopefully explain it :)/>
Bomb Bloke #7
Posted 26 May 2013 - 12:09 PM
I was afraid that was going to be it. :(/>

Ok, say the turtle puts a stack of combs in the centrifuge. It goes off and puts some lapis in the compressor (amount unprectable at the time it got the combs), maybe some iron dust in the furnace (amount unpredictable at the time it got the combs), goes back to the compressor with some glowstone dust (of an unpredicatable amount) 'cause the lapis doesn't take that long.

Now, say the centrifuge finished up while taking that load of glowstone dust to the compressor. The turtle clearly has absolutely no way of knowing the event was going to fire then (short of watching os.clock(), which takes the point out of setting a timer to tell it), but let's just say that's when it was due to go. Can it, or can it not, catch that event - or a rednet message - while actively moving and doing stuff? Can it catch said event or message after the current action (eg "turtle.forward()") is finished? If I setup a server to communicate with the turtle, can the server also be catching further timer events while it's in the process of transmitting?

I suspect that the answer to all of the above is "no", in which case I've started out with the correct impression of timer events and they're unsuitable for this scenario. If you need to stop everything to sit and catch events before they fire, then what you're looking at is merely a convenient way to have your program sleep - the goal here is to remove all "waiting around" from the script.

Thanks anyway, if this is the case. If my understanding is off and there IS a way to catch a timer event that fired while a system was doing something else, I'd indeed be interested to hear about it. While os.clock() does exactly what I want (assuming it's not the culprit of my server freezing…) I still consider it inelegant.
Engineer #8
Posted 26 May 2013 - 02:17 PM
I think coroutines are the way to go. Since Im currently in the train, I cant really help you. But the link leads to a great tutorial, it really helps you already.
If Im home tonight or tomorrow, Im going to look into on how to do it. It certainly is possible, its only more advanced :P/>
(I think you can use the parallel API, too. But I dont like that, I'd rather manage my coroutines myself. And this is actually practise for me too, because I havent really used coroutines)
Lyqyd #9
Posted 27 May 2013 - 02:48 AM
Coroutines are definitely the way to go here, and the parallel API will be sufficient for this case, I think.

I'm going to recap your use case; if I'm incorrect, my solution may not help, so please correct it. Essentially, what you're trying to do is use a single turtle for a set of time-sensitive tasks. You don't want the turtle to begin any of these tasks before they are ready, but it's not terribly urgent for any of these tasks to be started the moment they're ready to be performed again. All of these tasks involve turtle movements and/or other event-eating function calls (such as sleep), so you cannot rely on receiving timer events while the task is running.

The solution is to run a task management coroutine in parallel with your task performing code. It may sound a little scary, but it's just cooperative multitasking. Let's have a quick example:


local tasksTable = {}
local timersTable = {}

local function handleEvents()
    while true do
        event = {os.pullEvent()}
        if event[1] == "queueTask" then
            timersTable[event[2]] = os.startTimer(event[3])
        elseif event[1] == "timer" then
            for name, tID in pairs(timersTable) do
                if event[2] == tID then
                    table.insert(tasksTable, name)
                    timersTable[name] = nil
                    break
                    os.queueEvent("taskReady")
                end
            end
        end
    end
end

local function doStuff()
    local taskFunctions = {
        alvearyTask = function()
            turtle.forward()
            turtle.drop()
            turtle.back()
            os.queueEvent("queueTask", "alvearyTask", 15)
        end,
        otherTask = function()
            turtle.turnLeft()
            turtle.forward()
            turtle.suck()
            turtle.back()
            turtle.turnRight()
            os.queueEvent("queueTask", "differentTask", 5.2)
        end,
        differentTask = function()
            turtle.back()
            turtle.up()
            turtle.turnLeft()
            turtle.turnRight()
            turtle.down()
            turtle.forward()
            os.queueEvent("queueTask", "otherTask", 3.7)
        end,
    }
    while true do
        os.pullEvent("taskReady") --wait for a task to be ready
        if #tasksTable >= 1 then
            local taskName = table.remove(tasksTable, 1)
            if taskFunctions[taskName] then
                taskFunctions[taskName]()
            end
        end
    end
end

--initial task queueings to kick the system off.
os.queueEvent("queueTask", "alvearyTask", 0)
os.queueEvent("queueTask", "otherTask", 2)

parallel.waitForAny(handleEvents, doStuff)
Bomb Bloke #10
Posted 27 May 2013 - 09:48 PM
I'm going to recap your use case; if I'm incorrect, my solution may not help, so please correct it. Essentially, what you're trying to do is use a single turtle for a set of time-sensitive tasks. You don't want the turtle to begin any of these tasks before they are ready, but it's not terribly urgent for any of these tasks to be started the moment they're ready to be performed again. All of these tasks involve turtle movements and/or other event-eating function calls (such as sleep), so you cannot rely on receiving timer events while the task is running.
That's pretty much it, and I think your summary has provided me with the light bulb I've been looking for - you're implying that my turtle doesn't have to be sitting around doing nothing to catch an event at the time it fires, but can catch it later, yes (near instantly and with no delay, because it's already fired)?

Unless, that is, I'm using commands that "eat events". Presumably the timer event test I layed out earlier results in events being "eaten" while I'm delaying things by typing out commands (leading me to believe that all events are automatically eaten unless you try to catch them at the exact moment they're thrown), but if I put them together into a script and introduced some other delay - such as turtle movements - they wouldn't get eaten and I could catch them at my leisure?

In that case, what commands "eat" events? This is not something I noticed in the wiki's documentation, so I apologise if I missed it. I rather suspect I'm not using any of them (I'm certainly not using "sleep" - "no waiting around", remember!), and can plop the timers straight into my script without issue, but it'd save me some time to know beforehand (time being something I've been a bit short on so far this week :(/> ).

In the meantime, continued tests with my current script are indicating that it's indeed reliably stalling my server when active. One night I let it run and came back in the morning to find it still up - only to find that my turtle'd crashed due to a missed rednet signal. Doh!
BigSHinyToys #11
Posted 27 May 2013 - 11:06 PM
All turtle move commands will "eat" other events as well as sleep and read .

"eat" is realy the wrong term for it it ignores them instead then event dose happen just not then event the code wants so it ignores it.

Example :

local function sleep(nSec)
  local myTimer = os.startTimer(nSec)
  while true do
   local event,arg = os.pullEvent()
   if event == "timer" and arg == myTimer then
    break
   end
  end
end

As you can see if the event dos net match the one it wants it ignores it and waits for its one and only event to happen.

turtles are similar except they wait for a "turtle_response" event and this has a third arg tells us weather it happened or not.

the turtle.native functions don't "eat/ignore" events but they don't return true or false as they cant know if he move happened or not.

what you can do with coroutines is intercept all events filter them your self then sent them to the thread you want.

for example : FlipOS allows multiple different program to run simultaneously having one coroutine per program.

in your case you could have a coroutine that does stuff and use a series of flags to communicate with the coroutine
example

Ruff code untested

local mineFlag = false
local cropFlag = false
local cropTimer = os.startTimer(60)
local mineTimer = os.startTimer(40)
local co = coroutine.create(
function()
while true do
os.pullEvent()
if mineFlag then
mineFlag = false
-- code here
elseif cropFlag then
cropFlag = false
-- code here
else
print("noting to do but wait")
end
end
end
)
while true do
local event == {os.pullEvent()}
if event[1] == 'timer" then
if event[2] == cropTimer then
cropTimer = os.startTimer(40)
cropFlag = true
elseif event[2] == mineTimer then
mineTimer = os.startTimer(60) 
mineFlag = true
end
coroutine.resume(co,unpack(event))
end
Lyqyd #12
Posted 28 May 2013 - 01:06 AM
I'm going to recap your use case; if I'm incorrect, my solution may not help, so please correct it. Essentially, what you're trying to do is use a single turtle for a set of time-sensitive tasks. You don't want the turtle to begin any of these tasks before they are ready, but it's not terribly urgent for any of these tasks to be started the moment they're ready to be performed again. All of these tasks involve turtle movements and/or other event-eating function calls (such as sleep), so you cannot rely on receiving timer events while the task is running.
That's pretty much it, and I think your summary has provided me with the light bulb I've been looking for - you're implying that my turtle doesn't have to be sitting around doing nothing to catch an event at the time it fires, but can catch it later, yes (near instantly and with no delay, because it's already fired)?

Unless, that is, I'm using commands that "eat events". Presumably the timer event test I layed out earlier results in events being "eaten" while I'm delaying things by typing out commands (leading me to believe that all events are automatically eaten unless you try to catch them at the exact moment they're thrown), but if I put them together into a script and introduced some other delay - such as turtle movements - they wouldn't get eaten and I could catch them at my leisure?

In that case, what commands "eat" events? This is not something I noticed in the wiki's documentation, so I apologise if I missed it. I rather suspect I'm not using any of them (I'm certainly not using "sleep" - "no waiting around", remember!), and can plop the timers straight into my script without issue, but it'd save me some time to know beforehand (time being something I've been a bit short on so far this week :(/> ).

In the meantime, continued tests with my current script are indicating that it's indeed reliably stalling my server when active. One night I let it run and came back in the morning to find it still up - only to find that my turtle'd crashed due to a missed rednet signal. Doh!

I'll respond to each paragraph:

Yes, that's the idea. Each event sits in the event queue until the computer yields. It is resumed when there is another event in the queue for it. The event queue is managed on the Java side of things. Events will wait here forever.

That's correct. There are many functions that "eat" queued events. They do this because they are looking for a specific event to come in. They will repeatedly pull events from the queue (technically, yield to await the next event) until they find the one they are looking for. This destroys any events that arrived between the start of that function call and the time that the event it desires shows up.

Functions that "eat" events include read() (which you would have been using when typing commands), turtle calls (all turtle functions do this, as mentioned in BST's post above), sleep(), rednet.receive(), and a few others. Essentially, any function that takes any time to return or waits for anything to happen will "eat" events.
Bomb Bloke #13
Posted 28 May 2013 - 08:41 AM
Ok, I think I've got it: The event system isn't flaky in itself, but you can shoot yourself in the foot by pulling events in the wrong order - and especially when dealing with moving turtles, you have to pull a lot of events whether you want to or not.

Without testing it, I'm wondering if the parallel API method might run into problems… As far as I can tell, the two functions running side by side have to share the same event queue - meaning if they're both trying to pull events at the same time (say the turtle is moving, which apparently results in an event pull, and the other function is waiting for a timer event), it'd likely be quite random as to which function gets what events. I suppose they could fling them back into the queue if they don't want them and recognise that the other function might… Or am I really, really lucky and there's some reason the event system won't get confused?

Reading (er, skimming) the guide Engineer linked to, I'm also suspecting that co-routines might have massive troubles with turtles for much the same reason (it seems only the parent can get at the event queue, and what I really want is two event queues?). Though the existence of BST's FlipOS suggests that it should be possible to deal with it some how or other, and I reckon I'll find reading that code useful.

The simplest solution at this point is to dump the timing code onto my inventory server computer (which should have any event queue complications), but I'd like to have the turtle handle as much of its code as it can (simply for the sake of doing it that way). I'll have to read more!

Thanks all for your help so far! It's much appreciated! :)/>
Lyqyd #14
Posted 28 May 2013 - 01:22 PM
All events are available to both of the coroutines running in parallel.

Essentially, when a program pulls an event, it yields. What parallel does is resume each of its coroutines with each event it gets. Those run until they need a new event (or until ten seconds have elapsed), at which time, they yield to get their next event. Once the parallel API has looped through all of its child coroutines, resuming them with one event, it then itself yields to get a new event to distribute.

That way, even though your main program is moving around and doing stuff (and thereby "eating" events), the other coroutine is still seeing all of the events that come in.
Bomb Bloke #15
Posted 28 May 2013 - 09:43 PM
So the events get mirrored, and each function can pull them without hiding them from the other? Sounds perfect. The "ten seconds" thing sounds like a bit of a trap, but shouldn't affect my current project.
Bomb Bloke #16
Posted 30 May 2013 - 10:11 PM
I'd been putting it off due to lack of time (rebooting the server and rebuilding the turtle gets old fast, so I'd just left the turtle in a chest in the meanwhile), but this morning I've gotten around to implementing the parallel functions.

On the first boot, the turtle moved to its fuel station, but for some reason went to the wrong side (turns out I'd made a small change to its navigational code in error); I picked it up and replaced it, only to shortly find the server had stalled again (in fact I think I can now reliably trigger it by breaking my turtle, but it takes to long to test to say for sure right this moment).

Anyway, it's running now, but it's ignoring its timers.

The relevant code:

-- Job timer IDs. [1] = furnace, [2] = compression, [3] = centrifuge.
local worktimer = {0,0,0}

.
.
.

-- Burn baby burn!.
function cookStuff()
  -- Get something for the furnace, drag it there
  worktimer[1] = os.startTimer(turtle.getItemCount(1) * 6.55)
  -- Pickup/dropup previously furnaced stuff
end

.
.
.

-- Reset the timer table as need be, flagging jobs as ready.
function catchTimers()
	local event = ""
	local timerID = 0 
	while true do
		event, timerID = os.pullEvent("timer")
		for i=1,table.getn(worktimer) do if worktimer[i] == timerID then
			worktimer[i] = 0
			break
		end end
	end
end

-- Primary work loop.
function main()
	while true do
		if turtle.getFuelLevel() < 1000 then goGetFuel() end
		if carrying() then dropOff() end
		if getmagmafuel then fuelMagmaticEngines() end
		combineDustPiles()
		if worktimer[1] == 0 then cookStuff() end
		if worktimer[2] == 0 then compressStuff() end
		if worktimer[3] == 0 then processComb() end
	end
end

-- Initialisation stuff goes here, eg GPS and Rednet handshakes

.
.
.

parallel.waitForAny(main(), catchTimers())

I'm off to work and won't be able to play with it in earnest for at least another day, but I'm obviously doing something wrong here. The conditional check in the catchTimers function never comes up as true, indicating the relevant events are getting eaten despite the use of parallel functions. Tomorrow I've have time to write a scaled down version of the program to test things further, but I'll keep the current incarnation running in the meantime to see how stable it is.
BigSHinyToys #17
Posted 31 May 2013 - 02:46 AM
you are calling the function instead of giving them to the parallel function.
try


parallel.waitForAny(main, catchTimers) -- when you ues () it will call the function with out the () it is a variable that stores the function
Bomb Bloke #18
Posted 31 May 2013 - 10:53 AM
Aha! Lyqyd had specified as such. My mistake. Works fine that way, and breaking my turtle no longer breaks my server, too. :)/> Thanks!

Now to see if my server lasts the night!
Bomb Bloke #19
Posted 01 June 2013 - 01:09 AM
It didn't last the night. :(/>

I've dug around a bit, and this appears to be a dead match. I didn't introduce my timing system long after starting use of rednet, but doing so amplified the rate at which I sent rednet signals dramatically, so it's not unsurprising that I didn't get any crashes until "speeding things up".

Oddly enough, Cloudy reported that a fix'd been coded before the release of CC 1.5, which is what I'm using. He then later reported a coming fix again (after the release of CC 1.52), so presumably an update to CC 1.53 makes rednet usable… but that requires moving away from Minecraft 1.4.7!

Ah, decisions, decisions… Suspect I might just forget the whole thing until Minecraft 1.6. Hopefully this helps someone with a similar issue, however.
Lyqyd #20
Posted 01 June 2013 - 02:51 AM
Can we see your current rednet code? If you're being needlessly chatty, I'm sure there are ways to cut back. Hell, even nsh sessions don't trigger rednet-related issues with the server, and they are chatty.
Bomb Bloke #21
Posted 01 June 2013 - 04:06 AM
Well, the short answer is that whenever the turtle wants a given resource, it first sends a string to the inventory server describing that resource (eg "glowstone dust"), which then puts together a small table consisting of four numeric variables and a boolean (where to find the barrel and whether it's full) and sends it back as a string.

I wouldn't call this "needlessly chatty", but over the course of a few hours those messages would add up… And it seems that any amount of rednet usage will eventually kill a server, if it's looped. Unless you're on the very latest CC release (1.53)… I hope.

The main purpose of the inventory server is to determine whether a given barrel is full. I also piled the locational data into it because that could eventually allow me to run multiple turtles in the network without having to duplicate code. As the barrels aren't all in a single, straight, unbroken line, it got verbose quickly.

The long answer is, check the "getBarrel" function in the turtle's script and compare it to the code in the server script.

The "nodes" I refer to are locations that the turtle can safely travel between without fear of bumping into things, but the rest should be fairly straight-forward.

Thanks again. :)/>

Edit: Just struck me, I suppose I could try switching from the rednet API to the modem API. It may be immune.
Bomb Bloke #22
Posted 01 June 2013 - 07:59 PM
Earlier I'd mentioned my turtle sometimes didn't crash my Minecraft server, but instead crashed itself first by missing a rednet signal from the inventory server. I eventually accounted for that by switching this code:

	rednet.open("right")
	rednet.send(invserver,barreltype)
	invserver, message = rednet.recieve(5)
	rednet.close("right")

… with this code (which is what's in the pastebin'd stuff in my previous post):

	message = nil

	rednet.open("right")
	while message == nil do
		rednet.send(invserver,barreltype)
		invserver, message = rednet.recieve(5)
	end
	rednet.close("right")

I've now switched both scripts to use the modem API instead of the rednet API, but the turtle STILL sometimes misses transmissions from the server. The maddening thing is that the modem API relies on os.pullEvent, which unlike rednet.receive has no possiblity of a time out, so the turtle can't repeat its request and just sits there forever!

	modem.open(os.getComputerID())
	while message == nil do
		modem.transmit(invserver,os.getComputerID(),barreltype)
		event, messageside, incomingchannel, invserver, message = os.pullEvent("modem_message")
	end
	modem.close(os.getComputerID())

So no, my turtle didn't crash my server last night, but instead locked itself up waiting for an event that never came. I do have one clue though: both when using rednet and when using the modem API, it always fails when requesting info about the clay dust barrel. Only ever that one. I've no idea why, and the turtle can pull info about that barrel for hours before finally missing a message. The server does not stall and a reboot of the turtle is all that's required to get things moving again.

Updated scripts with the modem API: Turtle / Server.
Lyqyd #23
Posted 01 June 2013 - 08:17 PM
Why are you closing rednet?
Bomb Bloke #24
Posted 01 June 2013 - 08:43 PM
Doing so provides a visual indicator as to whether the turtle is using it. That is to say: For the same reason I'm using a turtle in the first place; 'cause it looks cool. :D/>

(And before you ask: those others reporting rednet-related server crashes did not do this, but rather just left it open all the time. It didn't seem to help them. Best I can make out, it's the actual transmissions that're the problem.)
Lyqyd #25
Posted 01 June 2013 - 09:09 PM
If you close rednet, any transmissions that get sent to it while it's closed won't be received.
Bomb Bloke #26
Posted 01 June 2013 - 09:14 PM
Which is exactly what I want, yes. Remember you're looking at the turtle's code above. It's only interested in direct responses to what it sends to the inventory server.

The inventory server, on the other hand, has to be ready to receive signals at very nearly any time, and so never closes rednet.

I guess that without a time out available on os.pullEvent, I'd need to set the modem up with parallel functions should I ever want it to deal with multiple clients. Maybe I should do that anyway, might make it more reliable.
Lyqyd #27
Posted 02 June 2013 - 12:45 AM
Hmm. I'll have to look a little deeper into it.

One point that you may find useful is creating a cross-reference table.


local combBarrelLookup = {}
local barrelLookup = {}

for x, row in ipairs(combbarrel) do
    for y, info in ipairs(row) do
        if info[1]:len() > 0 then
            combBarrelLookup[info[1]] = {x = x, y = y}
            if table.getn(info) > 1 then
                combBarrelLookup[info[1]].feeds = {}
                for i=2, table.getn(info) - 1, 2 do
                    table.insert(combBarrelLookup[info[1]].feeds, barrel[info[i]][info[i+1]][1])
                end
            end
        end
    end
end

for x, row in ipairs(barrel) do
    for y, info in ipairs(row) do
        if info[1]:len() > 0 then
            barrelLookup[info[1]] = {x = x, y = y}
            if table.getn(info) > 1 then
                barrelLookup[info[1]].feeds = {}
                for i=2, table.getn(info) - 1, 2 do
                    table.insert(barrelLookup[info[1]].feeds, barrel[info[i]][info[i+1]][1])
                end
            end
        end
    end
end

This would auto-generate your reference tables from your actual data (which means you only need to change the entry in one place), and you could then simplify your barrel lookup procedure, removing the two major loops:


if combBarrelLookup[incoming] then
    result[2] = combBarrelLookup[incoming].x > 2 and 600 or 593
    result[3] = -236 - combBarrelLookup[incoming].y
    result[4] = 66
    if combBarrelLookup[incoming].x == 2 or combBarrelLookup[incoming].x == 4 then result[4] = 63 end

    result[1] = 1
    result[5] = true
    if combBarrelLookup[incoming].feeds then
        result[5] = false
        for name in ipairs(combBarrelLookup[incoming].feeds) do
            if rs.testBundledInput(side[barrelLookup[name].x][1], 2^(barrelLookup[name].y + side[barrelLookup[name].x][2])) then result[5] = true end
        end
    end

    found = true

elseif barrelLookup[incoming] then
    result[2] = 575 + barrelLookup[incoming].y   -- X co-ord of barrel.
    if barrelLookup[incoming].x > 2 then result[2] = result[2] + 25 end

    result[3] = -236	  -- Y co-ord of barrel.

    result[4] = 66	    -- Z co-ord of barrel.
    if barrelLookup[incoming].x == 2 or barrelLookup[incoming].x == 4 then result[4] = 63 end

    result[1] = 12	    -- Node closest to barrel.
    if result[2] > 584 then result[1] = 10 end
    if result[2] > 592 then result[1] = 3 end
    if result[2] > 606 then result[1] = 5 end

    result[5] = true        -- Whether the barrels that hold the resources
                            -- produced from the contents of THIS barrel
                            -- can take more produce.

    if barrelLookup[incoming].feeds then
        result[5] = false
        for name in ipairs(barrelLookup[incoming].feeds) do
            if rs.testBundledInput(side[barrelLookup[name].x][1], 2^(barrelLookup[name].y + side[barrelLookup[name].x][2])) then result[5] = true end
        end
    end

    found = true

else
    print("Got a signal I didn't recognise: "..incoming)
    print("")
end

if found then
    modem.transmit(sender,os.getComputerID(),textutils.serialize(result))
end
Sorroko #28
Posted 02 June 2013 - 04:02 AM
Your server crashing because of the turtle sounds odd, could you post the ConcurrentModificationError or a larger section of your log? The socket exception is somewhat normal.
Bomb Bloke #29
Posted 02 June 2013 - 10:06 AM
Ran my turtle while I was out today, server survived again, but the turtle stalled again. Same barrel.

It occured to me that the solution to the "no time out on os.pullEvent while waiting for incoming modem signals" problem has an obvious solution (ironically so given the context of much of this thread), that being to set a timer event and see whether that gets pulled first.

-- Receive a message from a modem, or time out.
local function getModemMessage(timeout)
	local modemtimer = os.startTimer(timeout)
	
	while true do
		event, messageside, incomingchannel, invserver, message = os.pullEvent()
		if event == "modem_message" then return event, messageside, incomingchannel, invserver, message end
		if event == "timer" and messageside == modemtimer then return nil end
	end
end

So now I just treat that as I previously treated rednet.receive(). Sleep time now, I'll see what things look like in the morning.

Hmm. I'll have to look a little deeper into it.

One point that you may find useful is creating a cross-reference table.
Thanks, I'll read through it when I've got some more time tomorrow. Though I must admit that the only reason there's two sets of loops there in the first place is because I'm lazy. And don't poke too closely at my node pathing logic, the only reason it works at all is because there's only one possible path to any given node - I intend to re-write it so I can have my turtle take rather more complex routes once I expand my base somewhat.

Your server crashing because of the turtle sounds odd, could you post the ConcurrentModificationError or a larger section of your log? The socket exception is somewhat normal.
Sure, though I doubt the logged messages are related. Every instance of this reads the same (other then the timestamps):

2013-06-02 11:22:37 [INFO] [STDERR] java.util.ConcurrentModificationException
2013-06-02 11:22:37 [INFO] [STDERR] 	at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
2013-06-02 11:22:37 [INFO] [STDERR] 	at java.util.HashMap$ValueIterator.next(Unknown Source)
2013-06-02 11:22:37 [INFO] [STDERR] 	at bq.a(SourceFile:24)
2013-06-02 11:22:37 [INFO] [STDERR] 	at cd.a(SourceFile:114)
2013-06-02 11:22:37 [INFO] [STDERR] 	at ca.a(CompressedStreamTools.java:140)
2013-06-02 11:22:37 [INFO] [STDERR] 	at aam.a(AnvilChunkLoader.java:198)
2013-06-02 11:22:37 [INFO] [STDERR] 	at aam.c(AnvilChunkLoader.java:184)
2013-06-02 11:22:37 [INFO] [STDERR] 	at aiw.b(SourceFile:29)
2013-06-02 11:22:37 [INFO] [STDERR] 	at aiw.run(SourceFile:22)
2013-06-02 11:22:37 [INFO] [STDERR] 	at java.lang.Thread.run(Unknown Source)

Quite a few reports about it online but no one seems to know what it means (no, don't suggest Optifine, my server's not running Optifine ;)/>). It's not accompanied by any ill effects that I'm aware of (can fill my logs with that stuff for days with no issue).
Bomb Bloke #30
Posted 02 June 2013 - 07:40 PM
… My new modem implementation overwrote the variable tracking the inventory server's ID with nil if a transmission failed, making further transmissions impossible. At least it failed on a different barrel this time. Random bugs are such fun to troubleshoot!
Bomb Bloke #31
Posted 04 June 2013 - 07:53 AM
Still not sure I'm believing it, but she's been running for more then a day now. Methinks she's fixed!

Thanks to all! :)/>