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

infinite loop

Started by Thegameboy, 10 July 2016 - 04:52 PM
Thegameboy #1
Posted 10 July 2016 - 06:52 PM
I've been coding a menu api to take care of handling graphical objects, and I seem to have hit a bit of a bump. The render function is inside each menu object and if it hits an overlay (a menu inside another menu), it recursively calls itself until it finds no more. It gives me an ArrayIndexOutOfBoundsException once the rendering starts, which means that there are way more overlays than I thought. There are many (seemingly infinite) overlays inside of each other (always at index 1 of "overlays" table) despite a very simple create overlay function I thought only being capable of creating one at a time. How are they being created?


os.loadAPI "redirect"
os.loadAPI "general"
local cProperties = {
	check = "menu",
	x = 1,
	y = 1,
	width = 1,
	height = 1,
	bg = 1,
	fg = 1,
	text = "",
	textX = 1, --relative
	textY = 1,
	button = function() end,
	overlays = {}, --MUST be numerically indexed!!
	createLayer = function(self,properties)
		local menu = createMenu(properties)
		self.overlays[#self.overlays+1] = menu
	end,
	render = function(self,buffer)
		local characters = {}
	  
		do
			local c
			if self.text then
				for l=1,#self.text,1 do
					c = string.sub(self.text,l,l)
					table.insert(characters,c)
				end
			end
		end
	  
		local tx,ty = self.textX,self.textY
		local c
	  
	  
		for x=1,self.width,1 do
			for y=1,self.height,1 do
				if tx <= x then
					if ty == y then
						c = characters[x-tx+1]
					end
				end
				general.drawPixel(buffer,x+self.x,y+self.y,self.bg,self.fg,c)
			end
		end
	  
		for i=1,#self.overlays,1 do
			self.overlays[i]:render(buffer)
		end
	end
}
  
function createMenu(properties)
	local menu = {}
	properties = properties or {}
  
	for k,v in pairs(cProperties) do
		menu[k] = v
	end
	for k,v in pairs(properties) do
		menu[k] = v
	end
  
	return menu
end
local buffer = redirect.createRedirectBuffer(term.getWidth(),term.getHeight(),colors.white, colors.black, true)
local m = createMenu({x = 2})
m:createLayer()
m:render(buffer)
buffer.blit()
Edited on 10 July 2016 - 04:53 PM
Dragon53535 #2
Posted 10 July 2016 - 07:34 PM
My first guess is that you're incorrectly using colon syntax, given that the createLayer and render functions are not in the __index metamethod.

Try changing those two lines to m.createLayer() and m.render(buffer)
valithor #3
Posted 10 July 2016 - 07:55 PM
I can see you know that you need the loop that you used in createMenu to copy tables, or the variables you create will all point to the same table. The problem is that in your createMenu function you are setting variables equal to tables within tables, which follow the same rule for copying.

That was a painful way of saying… All of your menus/layers have the same overlays table inside of them.

To fix you could do something like this in your createMenu function:

function createMenu(properties)
		local menu = {}
		properties = properties or {}

		for k,v in pairs(cProperties) do
				menu[k] = v
		end
		menu[overlays] = {} --# added line here
		for k,v in pairs(properties) do
				menu[k] = v
		end

		return menu
end
Edited on 10 July 2016 - 05:56 PM
Thegameboy #4
Posted 10 July 2016 - 08:10 PM
I can see you know that you need the loop that you used in createMenu to copy tables, or the variables you create will all point to the same table. The problem is that in your createMenu function you are setting variables equal to tables within tables, which follow the same rule for copying.

That was a painful way of saying… All of your menus/layers have the same overlays table inside of them.

To fix you could do something like this in your createMenu function:

function createMenu(properties)
		local menu = {}
		properties = properties or {}

		for k,v in pairs(cProperties) do
				menu[k] = v
		end
		menu[overlays] = {} --# added line here
		for k,v in pairs(properties) do
				menu[k] = v
		end

		return menu
end

I'm not sure I understand what you're saying. How am I setting variables equal to tables within tables? Also, I applied the change and at least the error is gone for now. But if you could, I guess I'm asking if you could rephrase your answer a bit.
Edited on 10 July 2016 - 06:11 PM
valithor #5
Posted 10 July 2016 - 08:23 PM
I'm not sure I understand what you're saying. How am I setting variables equal to tables within tables? Also, I applied the change and at least the error is gone for now. But if you could, I guess I'm asking if you could rephrase your answer a bit.


for k,v in pairs(cProperties) do
  menu[k] = v
end

In this little snippet from your createMenu function you loop through each index in the cProperties table. One of these indexes is going to be overlays, where the value is the pointer to the table that you defined for overlays in the cProperties table (in Lua whenever we create a variable to represent a table, the variable contains a pointer to the table not the table itself). This means that anytime that, that loop runs and copies the overlays table they will have the same pointer in them, which essentially means that it is the same table. Sense it is the same table any modification to one of them will effect them all (they are all the same table… so it really isn't effecting them, but it is easier to think about it like that).

It might be worth playing around with this if my failed attempt at rewording didn't work :P/>


tbl = {innerTbl = {}, "hello"}
print(textutils.serialize(tbl))
innerTbl = tbl["innerTbl"] -- #basically what you are doing in that code snippet above
innerTbl["hello"] = "hi"
print(textutils.serialize(tbl))
Edited on 10 July 2016 - 06:24 PM
Thegameboy #6
Posted 10 July 2016 - 08:30 PM
I'm not sure I understand what you're saying. How am I setting variables equal to tables within tables? Also, I applied the change and at least the error is gone for now. But if you could, I guess I'm asking if you could rephrase your answer a bit.


for k,v in pairs(cProperties) do
  menu[k] = v
end

In this little snippet from your createMenu function you loop through each index in the cProperties table. One of these indexes is going to be overlays, where the value is the pointer to the table that you defined for overlays in the cProperties table (in Lua whenever we create a variable to represent a table, the variable contains a pointer to the table not the table itself). This means that anytime that, that loop runs and copies the overlays table they will have the same pointer in them, which essentially means that it is the same table. Sense it is the same table any modification to one of them will effect them all (they are all the same table… so it really isn't effecting them, but it is easier to think about it like that).

It might be worth playing around with this if my failed attempt at rewording didn't work :P/>


tbl = {innerTbl = {}, "hello"}
print(textutils.serialize(tbl))
innerTbl = tbl["innerTbl"] -- #basically what you are doing in that code snippet above
innerTbl["hello"] = "hi"
print(textutils.serialize(tbl))

Wow, that's funny actually. Yeah I knew that, it's the type of behavior I was counting on for the functions. Thanks!
The Crazy Phoenix #7
Posted 12 July 2016 - 04:08 PM

for k,v in pairs(cProperties) do
menu[k] = v
end
That will make menu a shallow copy of cProperties. If you want a deep copy (all contained tables are copied too), you'll want to run the function recursively for all keys and values of type table. As a result, changing one of the indexes in a table inside menu or cProperties won't affect the other.
Edited on 12 July 2016 - 02:08 PM
Dragon53535 #8
Posted 14 July 2016 - 02:41 AM

for k,v in pairs(cProperties) do
menu[k] = v
end
That will make menu a shallow copy of cProperties. If you want a deep copy (all contained tables are copied too), you'll want to run the function recursively for all keys and values of type table. As a result, changing one of the indexes in a table inside menu or cProperties won't affect the other.

You're misinterpreting what's happening I believe. When that loop is run, the value at menu[k] is set to a copy of cProperties[k] only if it's not a table or function. If cProperties[k] is a table or function, then the pointer to that is copied over. With functions it doesn't exactly matter, as you can't edit a function based on it's pointer as far as I know, however with tables that's where the problem lies. A table is really just a memory location with some data attached to it. If you set something to also look at that memory location, then when anything changes things at that location, the change is reflected on all instances pointing to it.

In this case lets say that cProperties[1] is a table that looks like {0,1,2}, if we were to run that loop, then menu[1][1] would be 0. Now let's edit menu[1][1] to be the string hello.

menu[1][1] = "hello"
Now the table inside cProperties[1] looks like {"hello",1,2} even though to our eyes we didn't actually touch it.

That's what's happening to his code. He's editing a table that all menu's look to. It's not a copy, it's just a different name.
Bomb Bloke #9
Posted 14 July 2016 - 02:49 AM
You're misinterpreting what's happening I believe. When that loop is run, the value at menu[k] is set to a copy of cProperties[k] only if it's not a table or function.

No, the value is always copied. The thing is that menu[k] will never actually contain a function or a table (or a coroutine) as a "value" - it'll hold a pointer leading to one of those objects, and that is why you'll also end up with a pointer in cProperties[k].

With functions it doesn't exactly matter, as you can't edit a function based on it's pointer as far as I know,

Although there's seldom cause to use it, setfenv() can indeed do that.