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

Melee turtles and os.pullEvent

Started by eastar, 09 July 2018 - 11:02 PM
eastar #1
Posted 10 July 2018 - 01:02 AM
Hi

I'm building a mobspawner.
Turtles would do the killing part but I can't seem to tackle an issue.
I just can't figure how to get the turtle to keep swinging his sword until an event occures.
I could probably resume the program by using another very fast timer to skip the waiting, but that's just doesn't seem like a proper way to do.
Could someone help me out with a more elegant solution?

So far this is what I got: (in pastebin)
Spoiler
inputPos = "bottom" --redstone input

-- event = 1
delayTime = 10 --Delay in seconds

opStatus = {["on"] = false, --turtle should attack when true
			["off"] = false, --turtle should NOT attack when true
			["delayedOff"] = false --turtle should attack when true
		   }
function attack()
if opStatus.on or opStatus.delayedOff then
  turtle.attack()
end
end
--[[
TODO: dropAll is too slow!!!
]]--
function dropAll()
for i=1,16 do
  turtle.select(i)
  turtle.drop()
end
end

--Displays statuses and warning.
function display()
term.clear()
term.setCursorPos(2,2)
print("I'm operating.")
term.setCursorPos(2,4)
for status,state in pairs (opStatus) do
  if state then
   print ("I'm in '".. status .."' state.")
   if not status == "off" then
	term.setCursorPos(2,5)
	print ("Don't go in front of me!")
   end
  end
end
end

--Main starts here

--[[Filling up opStatus table.
If redstone is active (mobspawner is off) upon load the turtle
stays on for delayTime seconds since it cannot be known if there
are any mobs in front of it.
--]]
if rs.getInput(inputPos) then
os.startTimer(delayTime)
opStatus.on = false
opStatus.off = false
opStatus.delayedOff = true
else
opStatus.on = true
opStatus.off = false
opStatus.delayedOff = false
end


while true do
display()

attack()

event = os.pullEvent()
if event == redstone then
  if rs.getInput(inputPos) then
--[[when rs input is active: delayedOff and
	start the delayTimer
]]--
   os.startTimer(delayTime)
   opStatus.on = false
   opStatus.off = false
   opStatus.delayedOff = true
  else
--when rs input is inactive: turn on
   opStatus.on = true
   opStatus.off = false
   opStatus.delayedOff = false
  end
elseif event == timer then
--when delayTimer is up: turn off
   opStatus.on = false
   opStatus.off = true
   opStatus.delayedOff = false
end
dropAll()

end

The code is not perfection and probably still got some (many?) flaws.
Mainly I'm focused on the
attack()

event = os.pullEvent()
part.

Thanks the help in advance!
Cheers! :-)
Bomb Bloke #2
Posted 10 July 2018 - 06:07 AM
ComputerCraft is event-driven - whenever your code needs to "wait" for anything, be it a timer or for a sword swing to finish, it "yields" execution and pauses until such time as it's "resumed" with event data.

For example, turtle.attack() works (loosely) along these lines:

function turtle.attack()
	local actionID = native.turtle.attack()  --# Hidden function we don't have access to in our own scripts,
						 --# which starts a sword swing and returns without waiting for it to finish.
	
	--# Then loops until an event with the result appears: 
	repeat
		local event, ID, result = os.pullEvent("turtle_event")
	until ID == actionID

	return result
end

One problem with this is that if a redstone_event occurs while this code is waiting for its turtle_event, it'll pull it from the front of the event queue and then discard it. You won't be able to pull it later in your code, so you'll actually miss some redstone state changes.

Luckily, the parallel API offers an easy workaround to this - it allows you to run multiple functions alongside each other as coroutines, passing copies of all events to each of them separately. While one coroutine is yielding the other can be actively executing code (which solves your waiting issues), and if one coroutine doesn't want a certain event, then that won't stop the other from reading its own copy (which solves your redstone issues).

local function swingSword()
	while true do
		turtle.attack()
	end
end

local function doRedstoneStuff()
	while true do
		local event = os.pullEvent()
		
		if event == "redstone" then  --# The quotes are important here!
			if rs.getInput(inputPos) then
				.
				.
				.
			end
		end
	end
end

parallel.waitForAny(swingSword, doRedstoneStuff)
eastar #3
Posted 11 July 2018 - 02:09 AM
Thank you!

I have made a short program to test the parallel workaround with the turtle. Although it is not the real program that I intend to use but the test worked!
Also thank you for pointing out the error I made at the event handling ( redstone / "redstone" ).

I have a couple of questons though…
I started tinkering with this parallel.waitForAny() function and realised that the reason I couldn't use the turtle.attack() is the same why I can't use sleep() with os.pullEvent().
sleep() expects a "timer" event just like turtle.attack() expects a "turtle_event".

So I made a small program using sleep(), os.pullEvent() and parallel.waitForAny() just for the sake of testing:
(pastebin)
Spoiler
--[[
local function debug(msg)
term.setCursorPos(1,18)
term.clearLine()
print(msg)
-- sleep(2)
end
]]--

local function redstone()
event = os.pullEvent()
if event == "redstone" then
  term.setCursorPos(1,2)
  term.clearLine()
  print(tostring(rs.getInput("right")))
end
end


local function sleeper()
-- debug ("sleeper started")
for i=1, 5 do
--  debug("in for cycle")
  term.setCursorPos(1,4)
  term.clearLine()
  print(i)
--  debug("before sleep")
  sleep(2)
--  debug("after sleep")
end
end

while true do
parallel.waitForAny(sleeper,redstone)
end

When it runs the following happens:
It prints 1
It reacts to the redstone events.
But the sleeper() function stops at sleep()

Why does it stop there?

Thank you again for your help!
Edited on 11 July 2018 - 12:10 AM
Bomb Bloke #4
Posted 11 July 2018 - 03:03 AM
parallel.waitForAny() runs all the supplied functions together until such time as "any" of them return. Your redstone() function returns after pulling its first event, so waitForAny() returns at that point as well, and the incomplete coroutine handling the sleeper() function is simply discarded.

If you want "all" the coroutines to run to completion, then use waitForAll(). If you want to loop the execution of your coroutines, then you'll probably be better off placing "while" loops inside their functions (so that they can repeat independently of each other).
eastar #5
Posted 11 July 2018 - 04:27 PM
Thank you so much! I got it now!
The redstone() function returned becouse it recieved the "timer" event from the sleep() in the sleeper() function. I didn't specify for the redstone() function to only look for "redstone" event. After that since the waitForAny was in a while loop it started all over again and again!

I'm enlightened now! :-)

Thank you again kind sir!
eastar #6
Posted 12 July 2018 - 05:37 PM
Hi again!

With your help I managed to write the program I needed. Thank you! :)/>

Before I finished the code I ran into a very weird problem to which I found an even weirder solution.

When one of the functions in parallel.waitForAny() has nothing to do the turtle just shuts down(!)
Why??? The whole code is in while loops and I don't use any break.
The solution I found for this is to put sleep() inside the functions. I can even put sleep(0) in there so the turtle will keep …not shutting down.
I really would like to know why is it like this? What's happening here?

The finished code:

Spoiler

--Turtle v1.2

--for testing
s = 1
--/for testing

inputPos = "bottom" --redstone input
delayTime = 10 --Delay in seconds
opStatus = {
			["on"] = false, --turtle should attack when true
			["off"] = false, --turtle should NOT attack when true
			["delayedOff"] = false --turtle should attack when true
		   }
displayTimerStart = true
displayRefreshRate = 1
delayTimerStart = true



function attack()
while true do
  if opStatus.on or opStatus.delayedOff then turtle.attack() end
  sleep(0) --no idea why it is needed but it do o.O
end
end


function dropAll()
while true do
  if opStatus.on or opStatus.delayedOff then
   for i=1,16 do
	turtle.select(i)
	turtle.drop()
   end
  end
sleep(0) --no idea why it is needed but it do o.O
end
end

--Displays statuses
function display()
term.clear()
term.setCursorPos(2,2)
print("I'm operating.")
term.setCursorPos(2,4)
for status,state in pairs (opStatus) do
  if state then
   print ("I'm in '".. status .."' state.")
   if not (status == "off") then
	term.setCursorPos(2,5)
	print ("Don't go in front of me!")
   end
  end
end
--for testing
term.setCursorPos(2,11)
term.write(tostring(s))
s = s+1
--/for testing
end

--Main starts here

function main()

--[[Filling up opStatus table.
If redstone is active (mobspawner is off) upon load the turtle
stays on for delayTime seconds since it cannot be known if there
are any mobs in front of it.
--]]
if rs.getInput(inputPos) then
   delayTimerID = os.startTimer(delayTime)
   opStatus.on = false
   opStatus.off = false
   opStatus.delayedOff = true
--for testing
   term.setCursorPos(2,10)
   term.clearLine()
   print("delayedOff ol")
--/for testing
else
  opStatus.on = true
  opStatus.off = false
  opStatus.delayedOff = false
--for testing
  term.setCursorPos(2,10)
  term.clearLine()
  print("On ol")
--/for testing
end


while true do
  if displayTimerStart then
   displayTimerStart = false
   displayTimerID = os.startTimer(displayRefreshRate)
  end

  event, p1 = os.pullEvent()
  if event == "redstone" then
   if rs.getInput(inputPos) then
--[[when rs input is active: delayedOff and
	 start the delayTimer
]]--
	delayTimerID = os.startTimer(delayTime)
	opStatus.on = false
	opStatus.off = false
	opStatus.delayedOff = true
--for testing
	term.setCursorPos(2,10)
	term.clearLine()
	print("delayedOff il")
--/for testing
   else
--when rs input is inactive: turn on
	opStatus.on = true
	opStatus.off = false
	opStatus.delayedOff = false
--for testing	
	term.setCursorPos(2,10)
	term.clearLine()
	print("on il")
--/for testing
   end
  elseif event == "timer" and p1 == delayTimerID and opStatus.delayedOff then
--when delayTimer is up and in delayOff state: turn off
   opStatus.on = false
   opStatus.off = true
   opStatus.delayedOff = false
--for testing
   term.setCursorPos(2,10)
   term.clearLine()
   print("Off il")
--/for testing
  elseif event == "timer" and p1 == displayTimerID then
   display()
   displayTimerStart = true
  end
end
end

parallel.waitForAny(main,attack,dropAll)
KingofGamesYami #7
Posted 12 July 2018 - 10:11 PM
You're triggering computercraft's yield prevention because your code is not yielding. This blocks all other parallel functions and all other computers in the world from executing until it is shut down.
Bomb Bloke #8
Posted 13 July 2018 - 04:56 AM
To expand on that - in reality, each ComputerCraft system in your Minecraft world is really just one of however many coroutines running within a single Lua VM. Only one coroutine is ever actually executing code at a time, and all the others have to keep on yielding until they "get their turn".

Normally code executes so fast that the switching process allows all your systems to appear to be running "at once" (and most systems spend most of their time yielding anyway). But if you write some code that doesn't yield for an extended period, then all the other systems will "freeze up" until its done.

Obviously that's a bad thing, regardless as to whether you're doing it on purpose or not, and so if ComputerCraft notices a coroutine running for more than about ten seconds it'll kill it. Sometimes it can just kill your script (in which case you'll see a "too long without yielding" error), but sometimes it has to kill your system's entire coroutine (in which case that device will simply turn off).
eastar #9
Posted 15 August 2018 - 01:53 PM
To expand on that - in reality, each ComputerCraft system in your Minecraft world is really just one of however many coroutines running within a single Lua VM. Only one coroutine is ever actually executing code at a time, and all the others have to keep on yielding until they "get their turn".

Normally code executes so fast that the switching process allows all your systems to appear to be running "at once" (and most systems spend most of their time yielding anyway). But if you write some code that doesn't yield for an extended period, then all the other systems will "freeze up" until its done.

Obviously that's a bad thing, regardless as to whether you're doing it on purpose or not, and so if ComputerCraft notices a coroutine running for more than about ten seconds it'll kill it. Sometimes it can just kill your script (in which case you'll see a "too long without yielding" error), but sometimes it has to kill your system's entire coroutine (in which case that device will simply turn off).

Hello!

Thank you very much!
It absolutely makes sense! :)/>