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

use parallel api to run multiple programs on one screen

Started by Jummit, 08 April 2018 - 05:54 PM
Jummit #1
Posted 08 April 2018 - 07:54 PM
I tried to run two programs at the same time, on a split screen, but I cant work out how to use term.redirect well. It always redirects it to one window for both programs. Here is the code:

local width, height = term.getSize()
local halfwidth = math.floor(width/2)
local window1 = window.create(term.current(), 1, 1, halfwidth, height)
local window2 = window.create(term.current(), halfwidth, 1, halfwidth, height)
parallel.waitForAll(
  function()
    --# I kow that this is only called once
    term.redirect(window1)
    shell.run("worm")
  end,
  function()
    --# I kow that this is only called once
    term.redirect(window2)
    shell.run("paint image")
  end
)
How can I manage to redirect the terminal in time so the right program can use it?
SquidDev #2
Posted 08 April 2018 - 08:01 PM
If you write out the control flow of this program, it makes a little bit more sense what happens:
  • Redirect to window1, run worm, worm yields
  • Redirect to window2, run paint, paint yields
  • Wait for an event
  • Resume worm with this event, worm yields
  • Resume paint with this event, paint yields,
  • etc…
Can you see what went wrong? We never redirected back into window1 when resuming after the first event, meaning both programs are stuck on window2. Sadly this cannot be fixed with parallel - you'll need to write your own coroutine manager which handles redirecting the terminal.

I'd recommend having a look at how multishell handles this, as well as the implementation of the parallel API. CraftOS's code isn't always the cleanest, but it's often a good starting point on how to implement features like this.
Jummit #3
Posted 08 April 2018 - 08:07 PM
If you write out the control flow of this program, it makes a little bit more sense what happens:
  • Redirect to window1, run worm, worm yields
  • Redirect to window2, run paint, paint yields
  • Wait for an event
  • Resume worm with this event, worm yields
  • Resume paint with this event, paint yields,
  • etc…
Can you see what went wrong? We never redirected back into window1 when resuming after the first event, meaning both programs are stuck on window2.
Yeah, I thought that would happen.
Sadly this cannot be fixed with parallel - you'll need to write your own coroutine manager which handles redirecting the terminal.

I'd recommend having a look at how multishell handles this, as well as the implementation of the parallel API. CraftOS's code isn't always the cleanest, but it's often a good starting point on how to implement features like this.
I will do that, but I remembered from last time I looked at it that it looks complicated.

Thanks btw for the quick reply! It was very helpful.
Jummit #4
Posted 08 April 2018 - 08:23 PM
How would you/is the multishell api approaching this?
SquidDev #5
Posted 08 April 2018 - 08:26 PM
How would you/is the multishell api approaching this?
You're not meant to use it. You're meant to read it, understand it, and then implement something similar. If there's something specific you get stuck on, we can help, but otherwise we're just going to end up writing the whole program for you, which benefits nobody.
Jummit #6
Posted 08 April 2018 - 08:29 PM
How would you/is the multishell api approaching this?
You're not meant to use it. You're meant to read it, understand it, and then implement something similar. If there's something specific you get stuck on, we can help, but otherwise we're just going to end up writing the whole program for you, which benefits nobody.
I wanted to know the concept behind the multishell api, how to let programs run in their terminals, not how to use it
Purple #7
Posted 08 April 2018 - 09:07 PM
Speaking of the API how does it handle function reentrancy? As in, can I trust my functions to be reentrent? Also does CC implement mutex locks and other synchronization tools?
SquidDev #8
Posted 08 April 2018 - 09:14 PM
Speaking of the API how does it handle function reentrancy? As in, can I trust my functions to be reentrent? Also does CC implement mutex locks and other synchronization tools?
Lua (and so CC) doesn't have preemptive multi-threading, but instead implements cooperative multithreading in the form of coroutines. This means only one thread* is ever running at once, and you have to explicitly change threads (through coroutine.yield). Consequently, you don't really need any fancy synchronisation schemes - you just need to be careful when and where you yield.

*It's worth noting that PUC Lua doesn't even use threads at all - a yield is little more than a jump into the parent coroutine's function. LuaJ does use threads, but that's an implementation detail one does not need to worry about.

For instance, consider the following Lua code:

local count = 0
parallel.waitForAll(function()
  while true do
	os.pullEvent("test") -- Yields here
	--# If Lua used preemptive multi-threading, this has the potential to be a race condition. However, + will not yield
	--# so it's fine.
	count = count + 1
  end
end, function()
  while true do
	sleep(math.random(1, 5)) --# Yields here
	count = count + 1

	--# Allows the above function to continue (due to test event). Note it won't actually start running until we're processing the
    --# "test" event, which may be several yields later.
	os.queueEvent("test")
  end
end)
Edited on 08 April 2018 - 07:21 PM
Jummit #9
Posted 09 April 2018 - 08:15 AM
I know this isn't working in the slightest, but here is the code I have so far:

local width, height = term.getSize()
local halfwidth = math.floor(width/2)
local window1 = window.create(term.current(), 1, 1, halfwidth, height)
local window2 = window.create(term.current(), halfwidth, 1, halfwidth, height)
local worm = coroutine.create(function()
  shell.run("worm")
end)
local paint = coroutine.create(function()
  shell.run("paint test")
end)
while true do
  term.redirect(window1)
  coroutine.resume(worm)
  term.redirect(window2)
  coroutine.resume(paint)
end

I get the typical too long without yielding error. What am I doing wrong here?
Jummit #10
Posted 09 April 2018 - 09:29 AM
So, why is this not working? Isn't sleep calling corouting.yield?

local co1 = coroutine.create(function()
  for i = 1, 10 do
    print("toast"..i)
    sleep(0.5)
  end
end)

local co2 = coroutine.create(function()
  for i = 1, 10 do
    print("test"..i)
    sleep(0.5)
  end
end)

local resume = function(co)
  local status = coroutine.status(co)
  if status == "normal" or status == "suspended" then
    coroutine.resume(co)
    print("resuming coroutine with status "..status)
  else
    print("not resuming coroutine with status "..status)
  end
end

while true do
  resume(co1)
  resume(co2)
end
Edited on 09 April 2018 - 07:35 AM
Lupus590 #11
Posted 09 April 2018 - 07:22 PM
Your coroutine manager doesn't yield, it needs to.
Edited on 09 April 2018 - 05:22 PM
KingofGamesYami #12
Posted 09 April 2018 - 07:35 PM
[shameless promotion]
You should review this
[/shameless promotion]
Jummit #13
Posted 09 April 2018 - 07:45 PM
Your coroutine manager doesn't yield, it needs to.
I thought os.pullevent, sleep and some other CC functions where yielding? Also, with the new code that yields still does not work:

local co1 = coroutine.create(function()
  for i = 1, 10 do
	print("toast"..i)
	sleep(0.5)
	coroutine.yield()
  end
end)
local co2 = coroutine.create(function()
  for i = 1, 10 do
	print("test"..i)
	sleep(0.5)
	coroutine.yield()
  end
end)
local resume = function(co)
  local status = coroutine.status(co)
  if status == "normal" or status == "suspended" then
	coroutine.resume(co)
  end
end
while true do
  resume(co1)
  resume(co2)
end
Edited on 09 April 2018 - 05:46 PM
KingofGamesYami #14
Posted 09 April 2018 - 08:11 PM
They do yield, to the code that called coroutine.resume. Which in this case is your infinate loop that never yields. See the diagram I posted above.
Jummit #15
Posted 09 April 2018 - 08:23 PM
[shameless promotion]
You should review this
[/shameless promotion]
Is this how cc uses coroutines to manage events or is this how coroutines work in cc?
KingofGamesYami #16
Posted 09 April 2018 - 08:54 PM
Both.
Jummit #17
Posted 10 April 2018 - 08:27 AM
I read this, this, this and looked at the coroutine functions in the devdocs lua wiki. Now I want to die.

I managed to get it to work:

local width, height = term.getSize()
local halfwidth = math.floor(width/2)
local window1 = window.create(term.current(), 1, 1, halfwidth, height)
local window2 = window.create(term.current(), halfwidth, 1, halfwidth, height)
local co1 = coroutine.create(function()
  os.run(_G, "rom/programs/fun/worm.lua")
end)
local co2 = coroutine.create(function()
  os.run(_G, "rom/programs/edit.lua", "test")
end)
while true do
  term.redirect(window1)
  coroutine.resume(co1, os.pullEventRaw())
  term.redirect(window2)
  coroutine.resume(co2, os.pullEventRaw())
end
But edit, paint and redirection always give the error 'loop in gettable'. What does this mean? The error comes from the shell api, but I don't know what happens in there.
EDIT: this explains everything
Edited code:
local width, height = term.getSize()
local halfwidth = math.floor(width/2)
local window1 = window.create(term.current(), 1, 1, halfwidth, height)
local window2 = window.create(term.current(), halfwidth, 1, halfwidth, height)

local co1 = coroutine.create(function()
  shell.run("rom/programs/fun/worm.lua")
end)

local co2 = coroutine.create(function()
  shell.run("rom/programs/edit.lua", "test")
end)

while true do
  local eventData = {os.pullEventRaw()}
  term.redirect(window1)
  coroutine.resume(co1, table.unpack(eventData))
  term.redirect(window2)
  coroutine.resume(co2, table.unpack(eventData))
end

Edited on 10 April 2018 - 08:15 AM
Purple #18
Posted 10 April 2018 - 11:20 AM
Lua (and so CC) doesn't have preemptive multi-threading, but instead implements cooperative multithreading in the form of coroutines. This means only one thread* is ever running at once, and you have to explicitly change threads (through coroutine.yield). Consequently, you don't really need any fancy synchronisation schemes - you just need to be careful when and where you yield.
Which is all well and good at times, but not always. Like say the simplest of all situations. You have a large loop that processes a large number of things so you want to break in the middle to give others a chance to do some work. But you don't want some other program touching your data.

I am of course operating from the perspective of writing each program individually and not wanting to care about what else they will be running along side with as I do so.
Lupus590 #19
Posted 10 April 2018 - 07:46 PM
Like say the simplest of all situations. You have a large loop that processes a large number of things so you want to break in the middle to give others a chance to do some work. But you don't want some other program touching your data.

Use local variables?