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

TE Energy Cell storage and usage monitoring system - issues

Started by xBlizzDevious, 26 October 2014 - 01:29 AM
xBlizzDevious #1
Posted 26 October 2014 - 02:29 AM
Hello everyone! I'm playing FTB Monster 1.1.2 and have manually updated all of the OpenMods to the latest (final?) versions for MC 1.6.4.

I've got an energy storage room with 125 Resonant Energy Cells all hooked up to a CC network (with wired modems) and to a computer that calculates the energy stored in said cells. Today (technically yesterday) I decided that I wanted to update my program to show me what my energy usage (or gain) was per tick.

1. My main bug - and my main reason for coming here - was that I was seeing my current energy stored every update… Major fail on my part as I realised - when I was copying my code to paste here - that I was setting one of my variables to a math.abs() of a different variable and not itself. So that's been fixed.

2. Are there any obvious bugs in my code that I've not seen? I'm reasonably confident with something like this, but I'm still a bit of a newbie at coding so I'd just like to check.

3. Timing… I had "sleep(2)" before but it always seemed a little longer than two seconds. Thinking that it must be the system slowing by iterating through 126 peripherals every 2 seconds, I changed it to 5 seconds. Now it seems nearer to 13 seconds to me. Is this a bug, my bad coding or something else?

4. Is it possible to calculate the energy used by my machines per tick? I guess not since I can only see what the storage is now and what it was before. I thought I would be able to use a "getEnergyOutputThisTick()" function or something like that, but only the energyStored and maxEnergyStored functions show up when I use getMethods().

5. Finally, in what ways could I optimise my program to a) run more efficiently and b ) for the code to look nicer and be easier to read? I like how easy it is now, but I constantly see code written by pros that's just so much "fancier" than anything I can write.

Here's the code in question:
Note: there is an 8 wide by 3 high monitor attached to the system too.


local ec = {}
local maxStore, curStore, storePercent, curUsed, lastStore = 0
local energyGained = true

function comma_value(n) -- credit http://richard.warburton.it
	local left,num,right = string.match(n,'^([^%d]*%d)(%d*)(.-)$')
	return left..(num:reverse():gsub('(%d%d%d)','%1,'):reverse())..right
end

function calcEnergyUsage()
	if lastStore == nil then
		lastStore = 1
	end
	
	curUsed = (curStore - lastStore)/100
	
	if curUsed < 0 then
		energyGained = false
	elseif curUsed > 0 then
		energyGained = true
	end
	
	curUsed = math.abs(curUsed)
end

while true do
	maxStore = 0
	curStore = 0
	storePercent = 0
	curUsed = 0
	
	for a,b in pairs(peripheral.getNames()) do
		if peripheral.getType(B)/>/>/> == "cofh_thermalexpansion_energycell" then
			ec[#ec+1] = peripheral.wrap(B)/>/>/>
			curStore = curStore + ec[#ec].getEnergyStored("")
			maxStore = maxStore + ec[#ec].getMaxEnergyStored("")
		end
		
		if peripheral.getType(B)/>/>/> == "monitor" then
			m = peripheral.wrap(B)/>/>/>
			term.redirect(m)
		end
	end
	storePercent = curStore/maxStore*100

	term.clear()
	term.setCursorPos(1, 1)
	
	local percentColour = 0
	
	if tonumber(storePercent) < 30 then
		percentColour = 16384
	elseif tonumber(storePercent) < 50 and tonumber(storePercent) > 30 then
		percentColour = 2
	elseif tonumber(storePercent) < 70 and tonumber(storePercent) > 50 then
		percentColour = 16
	elseif tonumber(storePercent) < 90 and tonumber(storePercent) > 70 then
		percentColour = 32
	elseif tonumber(storePercent) > 90 then
		percentColour = 2048
	end
	
	m.setTextScale(4)
	term.setTextColour(colors.white)
	print("Main Power Storage:	")
	
	term.setTextColour(percentColour)
	term.write(comma_value(curStore))
	term.setTextColour(colors.white)
	print(" RF of				")
	
	term.setTextColour(percentColour)
	term.write(comma_value(maxStore))
	term.setTextColour(colors.white)
	print(" RF				 ")
	
	term.setTextColour(percentColour)
	term.write(math.floor(storePercent).."%	")
	
	calcEnergyUsage()
	if lastStore ~= 0 then
		if energyGained == true then
			term.setTextColour(colors.lime)
		else
			term.setTextColour(colors.red)
		end
		term.write(comma_value(tostring(curUsed)))
		term.setTextColour(colors.white)
		print(" RF/t		   ")
	end
	lastStore = curStore
	
	sleep(5)
end

Thanks for taking the time to check it (and as you can see by the "comma_value" function's comments, it's not mine).
Edited on 26 October 2014 - 01:31 AM
KingofGamesYami #2
Posted 26 October 2014 - 03:32 AM
3. Timing… I had "sleep(2)" before but it always seemed a little longer than two seconds. Thinking that it must be the system slowing by iterating through 126 peripherals every 2 seconds, I changed it to 5 seconds. Now it seems nearer to 13 seconds to me. Is this a bug, my bad coding or something else?

This isn't a bug, and its not your coding either. The method getEnergyStored queues an event, and then waits for it, thus inciting a small lag factor. I've heard running the methods in parallel can speed it up, but I'm not sure.

There are three improvements I can see being made here

1 - use parallel
2 - use peripheral.call instead of wrapping, since you don't keep the wrapped object
3 - manually implement sleep for better timing (this allows the sleep time to ignore whatever time it took to call the methods)

Example of 3:

while true do
  --#define variables
  local id = os.startTimer( 5 ) --#set a timer to go off in five seconds
  --#do rest of code
  while true do --#infinate loop
    local _, tid = os.pullEvent( "timer" ) --#wait for the timer to go off
    if id == tid then --#if the timer went off
      break --#end the loop
    end
  end
end
Edited on 26 October 2014 - 02:33 AM
xBlizzDevious #3
Posted 26 October 2014 - 03:44 AM
3. Timing… I had "sleep(2)" before but it always seemed a little longer than two seconds. Thinking that it must be the system slowing by iterating through 126 peripherals every 2 seconds, I changed it to 5 seconds. Now it seems nearer to 13 seconds to me. Is this a bug, my bad coding or something else?

This isn't a bug, and its not your coding either. The method getEnergyStored queues an event, and then waits for it, thus inciting a small lag factor. I've heard running the methods in parallel can speed it up, but I'm not sure.

There are three improvements I can see being made here

1 - use parallel
2 - use peripheral.call instead of wrapping, since you don't keep the wrapped object
3 - manually implement sleep for better timing (this allows the sleep time to ignore whatever time it took to call the methods)

Example of 3:

while true do
  --#define variables
  local id = os.startTimer( 5 ) --#set a timer to go off in five seconds
  --#do rest of code
  while true do --#infinate loop
	local _, tid = os.pullEvent( "timer" ) --#wait for the timer to go off
	if id == tid then --#if the timer went off
	  break --#end the loop
	end
  end
end


So if I change this function:


for a,b in pairs(peripheral.getNames()) do
		if peripheral.getType(B)/>/>/>/>/>/> == "cofh_thermalexpansion_energycell" then
			ec[#ec+1] = peripheral.wrap(B)/>/>/>/>/>/>
			curStore = curStore + ec[#ec].getEnergyStored("")
			maxStore = maxStore + ec[#ec].getMaxEnergyStored("")
		end
		
		if peripheral.getType(B)/>/>/>/>/>/> == "monitor" then
			m = peripheral.wrap(B)/>/>/>/>/>/>
			term.redirect(m)
		end
	end

to:


	for a,b in pairs(peripheral.getNames()) do
		if peripheral.getType(B)/>/>/> == "cofh_thermalexpansion_energycell" then
			parallel.waitForAll(
			peripheral.call(b, curStore = curStore + b.getEnergyStored(""),
			peripheral.call(b, maxStore = maxStore + b.getMaxEnergyStored(""))
		end
		
		if peripheral.getType(B)/> == "monitor" then
		    term.redirect(B)/>
		end
	end


And implement your timer code - though probably changing the 5 to a 2 or 3?

Next question: should I just implement my monitor code outside of the while loop? It means that I can break, change and duplicate monitors without having to reboot my program, but to be honest, I don't see me doing that too often. The energy cell loop is handy though as I may well be adding a few cells every now and then.

And do you know the answers to my other questions (4 and 5)?

EDIT: What is with all this weird code appearing? Capitalised "b"s and "/>/>/>/>" appearing in my code?!
Edited on 26 October 2014 - 02:52 AM
KingofGamesYami #4
Posted 26 October 2014 - 03:52 AM
-snip-
So if I change this function:


for a,b in pairs(peripheral.getNames()) do
        if peripheral.getType(B)/> == "cofh_thermalexpansion_energycell" then
            ec[#ec+1] = peripheral.wrap(B)/>
            curStore = curStore + ec[#ec].getEnergyStored("")
            maxStore = maxStore + ec[#ec].getMaxEnergyStored("")
        end

        if peripheral.getType(B)/> == "monitor" then
            m = peripheral.wrap(B)/>
            term.redirect(m)
        end
    end

to:


    for a,b in pairs(peripheral.getNames()) do
        if peripheral.getType(B)/> == "cofh_thermalexpansion_energycell" then
            parallel.waitForAll(curStore = curStore + b.getEnergyStored("")
            parallel.waitForAll(maxStore = maxStore + b.getMaxEnergyStored("")
        end

        if peripheral.getType(B)/> == "monitor" then
            m = peripheral.wrap(B)/>
            term.redirect(m)
        end
    end


And simply swap my "sleep(5)" line for your above code (and probably change the 5 in the timer to a 2 or 3)?

And do you know the answers to my other questions (4 and 5)?

No.

Here's how I'd use parallel:

    local toRun = {} --#define a table
    for _,b in ipairs(peripheral.getNames()) do --#change to ipairs, eliminate a
        if peripheral.getType(B)/> == "cofh_thermalexpansion_energycell" then
            toRun[#toRun+1] = function() curStore = curStore + peripheral.call( b, "getEnergyStored", "" ) end --#change to peripheral.call, add new function to toRun
            toRun[#tRun+1] = function() maxStore = maxStore + peripheral.call( b, "getMaxEnergyStored", "") end --#same as above
        elseif peripheral.getType(B)/> == "monitor" then --#remove end, add elseif
            m = peripheral.wrap(B)/>
            term.redirect(m)
        end
    end
    parallel.waitForAll( unpack( toRun ) ) --#run contents of the table parallel to each other

And no you don't just swap "sleep(5)" for my code, what I did is I wrote the sleep( 5 ) function out in the individual functions, but I call os.startTimer before you collect the data, so it waits five seconds from the time the loop started, not five seconds from the time it gets to the end of the loop. My comments in the code show where each part should be positioned.

Edit: forum is messing with post, ignore all html stuff (if any, I think I removed it).
Edited on 26 October 2014 - 02:54 AM
Dragon53535 #5
Posted 26 October 2014 - 03:58 AM
Problem with your code Yami

	for a,b in pairs(peripheral.getNames()) do
		if peripheral.getType( B ) == "cofh_thermalexpansion_energycell" then
Should be

if peripheral.getType( b ) == "cofh_thermalexpansion_energycell" then


Darn you forums, nevermind, carry on.

Edit: There i fixed it, add spaces! MWAHAHAHAHA
Edited on 26 October 2014 - 02:59 AM
xBlizzDevious #6
Posted 26 October 2014 - 04:03 AM
Problem with your code Yami

	for a,b in pairs(peripheral.getNames()) do
		if peripheral.getType( B ) == "cofh_thermalexpansion_energycell" then
Should be

if peripheral.getType( b ) == "cofh_thermalexpansion_energycell" then


Darn you forums, nevermind, carry on.

Edit: There i fixed it, add spaces! MWAHAHAHAHA

Hahaha! Yeah, the changing code is annoying!

So I don't need the for loop? How's it going to iterate across 125 of them, then?
Dragon53535 #7
Posted 26 October 2014 - 04:05 AM
You do need the for, i just was trying to show that B isn't b, yeah keep the for there
KingofGamesYami #8
Posted 26 October 2014 - 04:07 AM
-snip-
So I don't need the for loop? How's it going to iterate across 125 of them, then?
hmm? Of course you need the for loop, it's filling the table with methods waiting to be called!
The parallel api iterates through them. [url="https://github.com/alekso56/ComputercraftLua/blob/master/rom/apis/parallel]source code of parallel[/url].
Bomb Bloke #9
Posted 26 October 2014 - 05:19 AM
The B)/>/>/> is presumably triggered by one of the extensions installed here (like the one which bugs out the search), given that I haven't seen it occur on other Invision boards. If you disable emoticons in your post you can avoid it.

The html thing can be "fixed" by mashing the rich-text editor toggle over and over quickly.

5. Finally, in what ways could I optimise my program to a) run more efficiently and b ) for the code to look nicer and be easier to read? I like how easy it is now, but I constantly see code written by pros that's just so much "fancier" than anything I can write.

Since you're asking for nitpicks, I suppose I'll nitpick. :P/>

In regards to your formatting: It really doesn't matter; go with what you find easiest to read. As it stands, you're formatting things nearly exactly the way I do (proper indentation with line breaks spacing out blocks of code), so personally I think that's great.

I'd ditch the calcEnergyUsage() function and move its contents down into the main "while" loop. Since you only call that code from one place, there's no benefit in having it bundled into a function at all - it slows your script down, makes it longer, and forces people trying to read it to scroll around the page.

Sometimes you might think it's a good idea to create a function just so you can "label" what a given segment of code does. If the segment is large enough (eg a couple of hundred lines), then I may do this myself on occasion. For a dozen lines of code… better just to use comments.

comma_value() is used well, but should be localised to the script.

You could set the text scale once, before your "while" loop starts, instead of setting it to the same thing over and over again inside said loop.

This check here:

        if curUsed < 0 then
                energyGained = false
        elseif curUsed > 0 then
                energyGained = true
        end

… will leave energyGained unaltered if curUsed = 0. If that's intended, then fine. If it's not, you could re-write it to just this:

        energyGained = curUsed > 0

If you decide to keep that bit as it is, then at least move the curUsed = math.abs(curUsed) line up into the "if" block. There's no point in running it unless curUsed < 0, which you just so happen to be specifically checking for anyway.

This block here:

        if tonumber(storePercent) < 30 then
                percentColour = 16384
        elseif tonumber(storePercent) < 50 and tonumber(storePercent) > 30 then
                percentColour = 2
        elseif tonumber(storePercent) < 70 and tonumber(storePercent) > 50 then
                percentColour = 16
        elseif tonumber(storePercent) < 90 and tonumber(storePercent) > 70 then
                percentColour = 32
        elseif tonumber(storePercent) > 90 then
                percentColour = 2048
        end

… will fail to do anything when storePercent is 30, 50, 70 or 90. There's also no point in the tonumber() call, given that storePercent is already a number - even if it weren't, better to convert it once and save it rather than converting it every time you want to do something with it.

Plus, some of the checks are redundant - each one you perform narrows the possibilities down, limiting the number of specific comparisons you need to make. That is to say, this'd work just fine:

        if storePercent < 30 then
                percentColour = 16384
        elseif storePercent < 50 then  -- We already know it's more than 30 by this point...
                percentColour = 2
        elseif storePercent < 70 then
                percentColour = 16
        elseif storePercent < 90 then
                percentColour = 32
        else
                percentColour = 2048
        end

Us of a ternary (and the fact that explicitly comparing energyGained to true is a redundant step) means we can shrink this:

                if energyGained == true then
                        term.setTextColour(colors.lime)
                else
                        term.setTextColour(colors.red)
                end

… to this:

                term.setTextColour(energyGained and colors.lime or colors.red)
xBlizzDevious #10
Posted 26 October 2014 - 12:04 PM
SpoilerThe B)/>/>/> is presumably triggered by one of the extensions installed here (like the one which bugs out the search), given that I haven't seen it occur on other Invision boards. If you disable emoticons in your post you can avoid it.

The html thing can be "fixed" by mashing the rich-text editor toggle over and over quickly.

5. Finally, in what ways could I optimise my program to a) run more efficiently and b ) for the code to look nicer and be easier to read? I like how easy it is now, but I constantly see code written by pros that's just so much "fancier" than anything I can write.

Since you're asking for nitpicks, I suppose I'll nitpick. :P/>

In regards to your formatting: It really doesn't matter; go with what you find easiest to read. As it stands, you're formatting things nearly exactly the way I do (proper indentation with line breaks spacing out blocks of code), so personally I think that's great.

I'd ditch the calcEnergyUsage() function and move its contents down into the main "while" loop. Since you only call that code from one place, there's no benefit in having it bundled into a function at all - it slows your script down, makes it longer, and forces people trying to read it to scroll around the page.

Sometimes you might think it's a good idea to create a function just so you can "label" what a given segment of code does. If the segment is large enough (eg a couple of hundred lines), then I may do this myself on occasion. For a dozen lines of code… better just to use comments.

comma_value() is used well, but should be localised to the script.

You could set the text scale once, before your "while" loop starts, instead of setting it to the same thing over and over again inside said loop.

This check here:

		if curUsed < 0 then
				energyGained = false
		elseif curUsed > 0 then
				energyGained = true
		end

… will leave energyGained unaltered if curUsed = 0. If that's intended, then fine. If it's not, you could re-write it to just this:

		energyGained = curUsed > 0

If you decide to keep that bit as it is, then at least move the curUsed = math.abs(curUsed) line up into the "if" block. There's no point in running it unless curUsed < 0, which you just so happen to be specifically checking for anyway.

This block here:

		if tonumber(storePercent) < 30 then
				percentColour = 16384
		elseif tonumber(storePercent) < 50 and tonumber(storePercent) > 30 then
				percentColour = 2
		elseif tonumber(storePercent) < 70 and tonumber(storePercent) > 50 then
				percentColour = 16
		elseif tonumber(storePercent) < 90 and tonumber(storePercent) > 70 then
				percentColour = 32
		elseif tonumber(storePercent) > 90 then
				percentColour = 2048
		end

… will fail to do anything when storePercent is 30, 50, 70 or 90. There's also no point in the tonumber() call, given that storePercent is already a number - even if it weren't, better to convert it once and save it rather than converting it every time you want to do something with it.

Plus, some of the checks are redundant - each one you perform narrows the possibilities down, limiting the number of specific comparisons you need to make. That is to say, this'd work just fine:

		if storePercent < 30 then
				percentColour = 16384
		elseif storePercent < 50 then  -- We already know it's more than 30 by this point...
				percentColour = 2
		elseif storePercent < 70 then
				percentColour = 16
		elseif storePercent < 90 then
				percentColour = 32
		else
				percentColour = 2048
		end

Us of a ternary (and the fact that explicitly comparing energyGained to true is a redundant step) means we can shrink this:

				if energyGained == true then
						term.setTextColour(colors.lime)
				else
						term.setTextColour(colors.red)
				end

… to this:

				term.setTextColour(energyGained and colors.lime or colors.red)

You see? This is what I mean. I have no idea that you can do these sorts of things. Thanks!
As for the Ternary thing, I vaguely remember that from back when I was at college, but that link doesn't help me understand it at all.


Now the thing that I can't figure out now, is how to run the getEnergyStored() and getMaxEnergyStored() functions in parallel and to fill in the variables as I need. So how would I go about that? This doesn't work (attempt to call nil) and I guess doesn't actually run in parallel either?


    for a,b in pairs(peripheral.getNames()) do
        if peripheral.getType(B)/> == "cofh_thermalexpansion_energycell" then
            curStore = curStore + parallel.waitForAll(peripheral.call(b, getEnergyStored("")))
            maxStore = maxStore + parallel.waitForAll(peripheral.call(b, getMaxEnergyStored("")))
        end
    end
Bomb Bloke #11
Posted 26 October 2014 - 01:25 PM
As for the Ternary thing, I vaguely remember that from back when I was at college, but that link doesn't help me understand it at all.

Make sure you read the expressions tutorial it links off to, especially the bit about logical operators. That same page also has a re-phrasing as to how Lua's approximation of ternarys work.

Even if you don't understand, I'm sure you can see how to use it. But don't worry too much - like I said in my disclaimer, I was just nit-picking.

Now the thing that I can't figure out now, is how to run the getEnergyStored() and getMaxEnergyStored() functions in parallel and to fill in the variables as I need. So how would I go about that? This doesn't work (attempt to call nil) and I guess doesn't actually run in parallel either?

You're attempting to call functions called getEnergyStored and getMaxEnergyStored (which don't actually exist in order to be called - at least, not in the way you're trying to call them!), before passing the results off to perperpheral.call() (the documentation of which you should read). Then there's your misuse of parallel.waitForAll(), which doesn't return anything of value here, needs to be passed function pointers (as opposed to the results of executed functions), and isn't worth using unless you're passing it multiple function pointers at once…

Let's take another look at the code KoGY posted for you earlier:

    local toRun = {} --#define a table
    for _,b in ipairs(peripheral.getNames()) do
        if peripheral.getType(b) == "cofh_thermalexpansion_energycell" then
            toRun[#toRun+1] = function() curStore = curStore + peripheral.call( b, "getEnergyStored", "" ) end  -- Make a function pointer, stick it in the toRun table
            toRun[#toRun+1] = function() maxStore = maxStore + peripheral.call( b, "getMaxEnergyStored", "") end
        elseif peripheral.getType(b) == "monitor" then
            m = peripheral.wrap(b)
        end
    end
    term.redirect(m)  -- Better to do this once

The above block fills the toRun table with functions. The first function will read along the lines of this:

toRun[1] = function()
        curStore = curStore + peripheral.call( "firstcell", "getEnergyStored", "" )
end

The next, like this:

toRun[2] = function()
        maxStore = maxStore + peripheral.call( "firstcell", "getMaxEnergyStored", "" )
end

The next, like this:

toRun[3] = function()
        curStore = curStore + peripheral.call( "secondcell", "getEnergyStored", "" )
end

And so on.

Whenever you call one of these functions, the peripheral call they perform will pause your script while waiting for OpenPeripheral to throw a special event. The idea is that you should be able to run other functions in the toRun table while waiting for others to complete, and the parallel API provides a simple method of doing so.

Hence, whenever you want to run ALL the functions in the table at once, you just do this:

curStore, maxStore = 0, 0
parallel.waitForAll(unpack(toRun))

This takes all the function pointers in the toRun table, and passes them off to the parallel API (using unpack() - make sure to read that whole page!), which then goes and actually runs them for you - making sure that whenever one stops to wait for an event, the next function is being started to avoid wasting time.

Note that depending on the nature of these events, attempting to throw and catch lots of them at the same time like this may just cause your big pile of functions to fall into a screaming heap. I guess try it, and see what happens.

If you're still unsure about the parallel API's workings, try reading this event tutorial before looking at the parallel API docs again.

To sum up, it's a short block of code, but there's a lot of complex operations to understand within it.
Edited on 26 October 2014 - 12:37 PM
xBlizzDevious #12
Posted 26 October 2014 - 04:20 PM
Spoiler
As for the Ternary thing, I vaguely remember that from back when I was at college, but that link doesn't help me understand it at all.

Make sure you read the expressions tutorial it links off to, especially the bit about logical operators. That same page also has a re-phrasing as to how Lua's approximation of ternarys work.

Even if you don't understand, I'm sure you can see how to use it. But don't worry too much - like I said in my disclaimer, I was just nit-picking.

Now the thing that I can't figure out now, is how to run the getEnergyStored() and getMaxEnergyStored() functions in parallel and to fill in the variables as I need. So how would I go about that? This doesn't work (attempt to call nil) and I guess doesn't actually run in parallel either?

You're attempting to call functions called getEnergyStored and getMaxEnergyStored (which don't actually exist in order to be called - at least, not in the way you're trying to call them!), before passing the results off to perperpheral.call() (the documentation of which you should read). Then there's your misuse of parallel.waitForAll(), which doesn't return anything of value here, needs to be passed function pointers (as opposed to the results of executed functions), and isn't worth using unless you're passing it multiple function pointers at once…

Let's take another look at the code KoGY posted for you earlier:

	local toRun = {} --#define a table
	for _,b in ipairs(peripheral.getNames()) do
		if peripheral.getType(B)/>/> == "cofh_thermalexpansion_energycell" then
			toRun[#toRun+1] = function() curStore = curStore + peripheral.call( b, "getEnergyStored", "" ) end  -- Make a function pointer, stick it in the toRun table
			toRun[#toRun+1] = function() maxStore = maxStore + peripheral.call( b, "getMaxEnergyStored", "") end
		elseif peripheral.getType(B)/>/> == "monitor" then
			m = peripheral.wrap(B)/>/>
		end
	end
	term.redirect(m)  -- Better to do this once

The above block fills the toRun table with functions. The first function will read along the lines of this:

toRun[1] = function()
		curStore = curStore + peripheral.call( "firstcell", "getEnergyStored", "" )
end

The next, like this:

toRun[2] = function()
		maxStore = maxStore + peripheral.call( "firstcell", "getMaxEnergyStored", "" )
end

The next, like this:

toRun[3] = function()
		curStore = curStore + peripheral.call( "secondcell", "getEnergyStored", "" )
end

And so on.

Whenever you call one of these functions, the peripheral call they perform will pause your script while waiting for OpenPeripheral to throw a special event. The idea is that you should be able to run other functions in the toRun table while waiting for others to complete, and the parallel API provides a simple method of doing so.

Hence, whenever you want to run ALL the functions in the table at once, you just do this:

curStore, maxStore = 0, 0
parallel.waitForAll(unpack(toRun))

This takes all the function pointers in the toRun table, and passes them off to the parallel API (using unpack() - make sure to read that whole page!), which then goes and actually runs them for you - making sure that whenever one stops to wait for an event, the next function is being started to avoid wasting time.

Note that depending on the nature of these events, attempting to throw and catch lots of them at the same time like this may just cause your big pile of functions to fall into a screaming heap. I guess try it, and see what happens.

If you're still unsure about the parallel API's workings, try reading this event tutorial before looking at the parallel API docs again.

To sum up, it's a short block of code, but there's a lot of complex operations to understand within it.

Thanks for those links - they've cleared up a few things.

Unfortunately, I still don't fully understand exactly how this works or how to implement it into my code to make it do what I'm after.

Here's what my code currently looks like:
Spoiler

local ec = {}
local maxStore, curStore, storePercent, curUsed, lastStore = 0
local energyGained = true

function comma_value(n) -- credit http://richard.warburton.it
	local left,num,right = string.match(n,'^([^%d]*%d)(%d*)(.-)$')
	return left..(num:reverse():gsub('(%d%d%d)','%1,'):reverse())..right
end
--Disabled to allow easier debugging
--[[
local m = peripheral.find("monitor")
if m then
	m.setTextScale(4)
	term.redirect(m)
end
]]--
while true do
	maxStore, curStore, storePercent, curUsed = 0
	local id = os.startTimer( 5 ) --#set a timer to go off in five seconds
	
	local toRun = {} --#define a table
	for _,b in ipairs(peripheral.getNames()) do
		if peripheral.getType(B)/>/> == "cofh_thermalexpansion_energycell" then
			toRun[#toRun+1] = function() curStore = curStore + peripheral.call(b, "getEnergyStored", "") end  -- Make a function pointer, stick it in the toRun table
			toRun[#toRun+1] = function() maxStore = maxStore + peripheral.call(b, "getMaxEnergyStored", "") end
		end
	end
	parallel.waitForAll(unpack(toRun))

	storePercent = curStore/maxStore*100

	term.clear()
	term.setCursorPos(1, 1)
	
	local percentColour = 0
	
	if storePercent < 30 then
		percentColour = 16384
	elseif storePercent < 50 then
		percentColour = 2
	elseif storePercent < 70 then
		percentColour = 16
	elseif storePercent < 90 then
		percentColour = 32
	else
		percentColour = 2048
	end
	
	term.setTextColour(colors.white)
	print("Main Power Storage:	")
	
	term.setTextColour(percentColour)
	term.write(comma_value(curStore))
	term.setTextColour(colors.white)
	print(" RF of				")
	
	term.setTextColour(percentColour)
	term.write(comma_value(maxStore))
	term.setTextColour(colors.white)
	print(" RF				 ")
	
	term.setTextColour(percentColour)
	term.write(math.floor(storePercent).."%	")
	
	if lastStore == nil then
		lastStore = 1
	end
	
	curUsed = (curStore - lastStore)/100
	
	energyUsed = curUsed > 0
	
	curUsed = math.abs(curUsed)
	
	if lastStore ~= 0 then
		term.setTextColour(energyGained and colors.lime or colors.red)
		term.write(comma_value(curUsed))
		term.setTextColour(colors.white)
		print(" RF/t		   ")
	end
	lastStore = curStore
	
	while true do --#infinite loop
		local _, tid = os.pullEvent( "timer" ) --#wait for the timer to go off
		if id == tid then --#if the timer went off
			break --#end the loop
		end
	end
end

But if I run that, I get:

parallel:7: vm error:
java.lang.ArrayIndexOutOfBoundsException

Another thing I'm curious about: in the original code, I had: "for a,b in pairs" but in the code KoGY posted, he's used: "for a,b in ipairs". What's the difference?

Thanks for all the help so far. I'd quite like to learn a lot more about programming but sometimes I just can't get my head around things.
Edited on 26 October 2014 - 03:22 PM
KingofGamesYami #13
Posted 26 October 2014 - 05:01 PM
-snip-
Another thing I'm curious about: in the original code, I had: "for a,b in pairs" but in the code KoGY posted, he's used: "for a,b in ipairs". What's the difference?
-snip-

ipairs basically means 'iterated pairs', and can only be used when a table is numerically indexed. It will iterate through the table's numeric indexes, eg [1], [2], etc. until it finds one that is nil.

pairs is used when a table doesn't have numerical indexes, or when you don't have to move in order.

In your case, it does not matter which you use, I just prefer to use ipairs when possible, thus my change.

Edit:

As to the error you posted, I'm not sure whats causing that. That error usually shows up when someone uses improper recursion, but we didn't do that here.
Edited on 26 October 2014 - 05:54 PM
xBlizzDevious #14
Posted 26 October 2014 - 07:12 PM
-snip-
Another thing I'm curious about: in the original code, I had: "for a,b in pairs" but in the code KoGY posted, he's used: "for a,b in ipairs". What's the difference?
-snip-

ipairs basically means 'iterated pairs', and can only be used when a table is numerically indexed. It will iterate through the table's numeric indexes, eg [1], [2], etc. until it finds one that is nil.

pairs is used when a table doesn't have numerical indexes, or when you don't have to move in order.

In your case, it does not matter which you use, I just prefer to use ipairs when possible, thus my change.

Edit:

As to the error you posted, I'm not sure whats causing that. That error usually shows up when someone uses improper recursion, but we didn't do that here.

Good to know about the pairs and ipairs.

About the error; that's what I thought and I was pretty certain that I know what's going on with this code. Which is why I was so confused. Could it be to do with what @Bomb Bloke alluded to earlier:
Note that depending on the nature of these events, attempting to throw and catch lots of them at the same time like this may just cause your big pile of functions to fall into a screaming heap. I guess try it, and see what happens.
Bomb Bloke #15
Posted 26 October 2014 - 09:56 PM
As to the error you posted, I'm not sure whats causing that. That error usually shows up when someone uses improper recursion, but we didn't do that here.

More precisely, you get it when trying to cram more than 256 function calls into the stack at once. If there are two functions for each cell in toRun, and there are 125 cells, that's 250 calls right there. Given that bios.lua keeps a few calls floating in the stack (shell and rednet for example), that's pretty iffy just on its own, but once you add on the additional peripheral.call calls (one per toRun function) you can see that blows way past the limit.

Dividing up the functions into multiple tables (eg one for adding up curStore and one for adding up maxStore) may do the trick (you'd then use two parallel.waitForAll() calls to deal with them, halving the load on each). It may be they need to be divided up further than that.
xBlizzDevious #16
Posted 26 October 2014 - 10:19 PM
As to the error you posted, I'm not sure whats causing that. That error usually shows up when someone uses improper recursion, but we didn't do that here.

More precisely, you get it when trying to cram more than 256 function calls into the stack at once. If there are two functions for each cell in toRun, and there are 125 cells, that's 250 calls right there. Given that bios.lua keeps a few calls floating in the stack (shell and rednet for example), that's pretty iffy just on its own, but once you add on the additional peripheral.call calls (one per toRun function) you can see that blows way past the limit.

Dividing up the functions into multiple tables (eg one for adding up curStore and one for adding up maxStore) may do the trick (you'd then use two parallel.waitForAll() calls to deal with them, halving the load on each). It may be they need to be divided up further than that.

Ah… Well I think it must be OK with how many it's got now as it seems to be happy to give me a different error! I've made two tables (current and maxi) and populated them with the functions to add up their respective amounts. Outside of the for loop, I've got a line to run through the "current" table and another to run through the "maxi" table.

This is the error I get:

startup:27: attempt to perform arithmetic __add on nil and number

Which makes me think that there must be a syntax error, but from looking at the peripheral.call page, it seems that my syntax is correct.


    for _,b in ipairs(peripheral.getNames()) do
        if peripheral.getType(B)/> == "cofh_thermalexpansion_energycell" then
            current[#current+1] = function() curStore = curStore + peripheral.call(b, "getEnergyStored", "") end  --Line 27
            maxi[#maxi+1] = function() maxStore = maxStore + peripheral.call(b, "getMaxEnergyStored", "") end
        end
    end
Edited on 26 October 2014 - 09:20 PM
Bomb Bloke #17
Posted 26 October 2014 - 10:24 PM
It's saying "nil" and "number", not "number" and "nil". Likely it's complaining that you never predefined curStore as 0.
xBlizzDevious #18
Posted 26 October 2014 - 10:25 PM
It's saying "nil" and "number", not "number" and "nil". Likely it's complaining that you never predefined curStore as 0.

Interesting. I had the same issue with something else before. This line doesn't seem to do its job very well:
    maxStore, curStore, storePercent, curUsed = 0

How do I fix that?
Dragon53535 #19
Posted 26 October 2014 - 10:30 PM

maxStore, curStore, storePercent, curUsed = 0,0,0,0
You have to set the value for all, the way you have it is maxstore has a value and the rest don't

Edit: Rekt BB
Edited on 26 October 2014 - 09:31 PM
Bomb Bloke #20
Posted 26 October 2014 - 10:30 PM
Edit: :ph34r:/>'d
Edited on 26 October 2014 - 09:31 PM
xBlizzDevious #21
Posted 26 October 2014 - 10:42 PM

maxStore, curStore, storePercent, curUsed = 0,0,0,0
You have to set the value for all, the way you have it is maxstore has a value and the rest don't

Edit: Rekt BB
Edit: :ph34r:'d

Ah! Didn't realise that. Thanks!

I GOT IT WORKING…. Mostly.

It seems to only add up one now. Here's the code followed by a screenshot of the output. There's 125 RECs connected (6.25 billion).

Spoiler

local ec = {}
local maxStore, curStore, storePercent, curUsed, lastStore = 0
local energyGained = true

function comma_value(n) -- credit http://richard.warburton.it
	local left,num,right = string.match(n,'^([^%d]*%d)(%d*)(.-)$')
	return left..(num:reverse():gsub('(%d%d%d)','%1,'):reverse())..right
end

local m = peripheral.find("monitor")
if m then
	m.setTextScale(4)
	term.redirect(m)
end

while true do
	maxStore, curStore, storePercent, curUsed = 0, 0, 0, 0
	local id = os.startTimer( 5 ) --#set a timer to go off in five seconds
	
	local current = {} --#define a table
	local maxi = {}
	
	for _,b in ipairs(peripheral.getNames()) do
		if peripheral.getType(B)/> == "cofh_thermalexpansion_energycell" then
			current[#current+1] = function() curStore = curStore + peripheral.call(b, "getEnergyStored", "") end  --Line 27
			maxi[#maxi+1] = function() maxStore = maxStore + peripheral.call(b, "getMaxEnergyStored", "") end
		end
	end
	parallel.waitForAll(unpack(current))
	parallel.waitForAll(unpack(maxi))

	storePercent = curStore/maxStore*100

	term.clear()
	term.setCursorPos(1, 1)
	
	local percentColour = 0
	
	if storePercent < 30 then
		percentColour = 16384
	elseif storePercent < 50 then
		percentColour = 2
	elseif storePercent < 70 then
		percentColour = 16
	elseif storePercent < 90 then
		percentColour = 32
	else
		percentColour = 2048
	end
	
	term.setTextColour(colors.white)
	print("Main Power Storage:	")
	
	term.setTextColour(percentColour)
	term.write(comma_value(curStore))
	term.setTextColour(colors.white)
	print(" RF of				")
	
	term.setTextColour(percentColour)
	term.write(comma_value(maxStore))
	term.setTextColour(colors.white)
	print(" RF				 ")
	
	term.setTextColour(percentColour)
	term.write(math.floor(storePercent).."%	")
	
	if lastStore == nil then
		lastStore = 1
	end
	
	curUsed = (curStore - lastStore)/100
	
	energyUsed = curUsed > 0
	
	curUsed = math.abs(curUsed)
	
	if lastStore ~= 0 then
		term.setTextColour(energyGained and colors.lime or colors.red)
		term.write(comma_value(curUsed))
		term.setTextColour(colors.white)
		print(" RF/t		   ")
	end
	lastStore = curStore
	
	while true do --#infinite loop
		local _, tid = os.pullEvent( "timer" ) --#wait for the timer to go off
		if id == tid then --#if the timer went off
			break --#end the loop
		end
	end
end


Do I need to have the parallel function in the for loop? I thought the "unpack" thing iterated through the table itself?
Edited on 26 October 2014 - 09:43 PM
KingofGamesYami #22
Posted 26 October 2014 - 10:56 PM
Do I need to have the parallel function in the for loop? I thought the "unpack" thing iterated through the table itself?

1. no
2. no unpack doesn't iterate through the table, it passes the contents to parallel.waitForAll, which then turns them into coroutines, starts collecting yield data and calling them in a loop until all have finished their respective tasks. I'm not sure why it'd only add up one, my first guess was you used parallel.waitForAny, which waits for only one function to be called, but your code doesn't match this theory. My second guess is only one is actually connected…
Edited on 26 October 2014 - 09:57 PM
xBlizzDevious #23
Posted 26 October 2014 - 11:32 PM
Do I need to have the parallel function in the for loop? I thought the "unpack" thing iterated through the table itself?

1. no
2. no unpack doesn't iterate through the table, it passes the contents to parallel.waitForAll, which then turns them into coroutines, starts collecting yield data and calling them in a loop until all have finished their respective tasks. I'm not sure why it'd only add up one, my first guess was you used parallel.waitForAny, which waits for only one function to be called, but your code doesn't match this theory. My second guess is only one is actually connected…

Your second guess was my first guess. And yeah, there's 125 connected. Your other guess was something I'd played around with earlier, but the code you see there is exactly what I have here right now.
KingofGamesYami #24
Posted 27 October 2014 - 12:31 AM
Go into the lua prompt and type this:

lua> textutils.pagedTabulate( peripheral.getNames() )
xBlizzDevious #25
Posted 27 October 2014 - 02:03 AM
Go into the lua prompt and type this:

lua> textutils.pagedTabulate( peripheral.getNames() )

Yep. Definitely a LOT of Energy Cells connected.

Any ideas?
KingofGamesYami #26
Posted 27 October 2014 - 02:16 AM
Nothing other than the usual, add print statements to different sections of the code to figure out where it's going wrong, etc.

#current should help a lot in tracking down the bug.
Bomb Bloke #27
Posted 27 October 2014 - 06:30 AM
Indeed, print statements. Eg change this sorta code like so:

current[#current+1] = function() curStore = curStore + peripheral.call(b, "getEnergyStored", "") print("curCheck: "..B)/> end

Speaking of which, this whole block here:

        local current = {} --#define a table
        local maxi = {}

        for _,b in ipairs(peripheral.getNames()) do
                if peripheral.getType(B)/>/> == "cofh_thermalexpansion_energycell" then
                        current[#current+1] = function() curStore = curStore + peripheral.call(b, "getEnergyStored", "") end  --Line 27
                        maxi[#maxi+1] = function() maxStore = maxStore + peripheral.call(b, "getMaxEnergyStored", "") end
                end
        end

… doesn't really need to be in your "while" loop. The only circumstance in which you'd want the script to constantly spend time looking for peripherals and rewriting the current/maxi tables would be if you're constantly adding/removing peripherals. Sure, you may do that sometimes, but no where near often enough to justify spamming that bit of expensive code (it has to compile something like 250 functions every time it's processed, after all!).

While there is an efficient way to have the script rebuild the tables in response to peripheral changes, it's not really worth the effort.

Even this line here doesn't really need to be executed more than once:

parallel.waitForAll(unpack(maxi))

This chunk here:

        if lastStore == nil then
                lastStore = 1
        end

… would be better written as:

        if not lastStore then lastStore = 1 end

… or even just:

        lastStore = lastStore or 1

This loop here:

        while true do --#infinite loop
                local _, tid = os.pullEvent( "timer" ) --#wait for the timer to go off
                if id == tid then --#if the timer went off
                        break --#end the loop
                end
        end

… would be better phrased as a repeat loop:

        repeat local _, tid = os.pullEvent( "timer" ) until id == tid