Storing vector positions in a Set, contains() always false, logic flaw

Hi there!
I would like to ask for some help. There is a logic flaw which I cannot figure out…
I’m creating a couple of objects with a factory and I want all of them to be at random but different places.
I’m using a Set to store the vector3 positions and before creating a new object I would like to check if the Set already contains it. I’m using this implementation which should work fine. However the check always returns false. Also debugged it, expected 50 objects were created but at less then 50 positions.
I think the issue is somewhere around the storing local position variable.
Here is the code snippet:

local wallPositionSet = Set.new()
function initMaze()
	local counter = NULL
	repeat
		local position = vmath.vector3(NULL, NULL, ONE)
		position.x = math.random(FIRST_INNER_COL, LAST_INNER_COL)
		position.y = math.random(FIRST_INNER_ROW, LAST_INNER_ROW)
		if not wallPositionSet.contains(wallPositionSet, position) then
			factory.create("#maze", position)
			wallPositionSet.insert(wallPositionSet, position)
			counter = counter + ONE
		end
	until(counter == FIFTY)
end

Thank you in advance!

I think you are overcomplicating things. First, there is no NULL in Lua. Then tables can act as a set.
And don’t use global variables, your initMaze() function is global.

I’d write something like this.

local walls = {}
local function initMaze()
	for i = 1, 50 do
		local is_placed = false
		repeat
			local x, y = math.random(FIRST_INNER_COL, LAST_INNER_COL), math.random(FIRST_INNER_ROW, LAST_INNER_ROW)
			if not walls[x + y * width] then
				walls[x + y * width] = true
				is_placed = true
				factory.create("#maze", vmath.vector3(x, y, 1))
			end
		until is_placed
	end
end

width is the maze width.

The reason why it’s not working with that Set module is because vectors are passed around by reference, not by value. Each vector you create is a different object, even if they have exactly the same values. That means if you use vectors as table keys (like your Set module), if you use two vectors with exactly the same values, they will still be recognized as different keys. You can check this with a table:

	local v1 = vmath.vector3(1, 2, 3)
	local v2 = vmath.vector3(1, 2, 3)
	local t = {}
	t[v1] = true

	print(t[v1]) -- Will be true.
	print(t[v2]) -- Will be nil.

	t[v2] = true
	pprint(t) -- Will give this:
	-- { --[[0000000002160340]]
	--	vmath.vector3(1, 2, 3) = true,
	--	vmath.vector3(1, 2, 3) = true
	-- }

Defold does specifically allow you to compare vectors with ‘==’, but that’s a special case.

(I’m also confused why you are using variables for literal numbers and “NULL”…but to each their own I guess.)

Thank you for confirming, that was my guess as well.

(regarding variables for literals: I’m a Java backend dev and on my project we have strict code-style rules, ex. not to use ‘magic numbers’ in code and to declare them as constants :sweat_smile: )

Thanks for the fast reply and advice, I will try it out.
I’m still trying to get familiar with table as I’m used to Java’s set, array, map, etc. :sweat_smile:

(about NULL: it is just a habit from my current project but it would have been more reasonable to call it ZERO as it stands for 0 and not nil)

The Lua table is an interesting data structure. You can use it as all three of the above:

local set = {}
set["foo"] = true
set["bar"] = true
print("Set has foo: ", set["foo"]) -- true
for v,_ in pairs(set) do
    print(v)
end

local array = { "foo", "bar" }
print(array[1]) -- "foo"
for i=1,#array do
    print(i, array[i])
end
for i,v in ipairs(array) do
    print(i, v)
end

local map = {}
map["foo"] = 42
map["bar"] = 10101
print("Map value for key foo is:", map["foo"]) -- 42
for k,v in pairs(map) do
    print(k, v)
end
3 Likes

Thank you for the examples, really helpful.

1 Like

A bit off-topic here, but doesn’t using FIFTY instead of 50 still count as using a ‘magic number’? Rather than MAZE_WALL_COUNTor something? Just curious. I’m not a professional programmer myself.

You are absolutely right, it would.
I’m my code it is actually called MAZE_DENSITY, just changed it for the post here to make it more readable.

2 Likes

Java and Lua are quite different. Some Java habits don’t translate well.
For instance enums are quite pointless in my opinion.

local enums = {
  STATE_ONE = 1,
  STATE_TWO = 2,
  STATE_THREE = 3
}

Calling a function with such enum

call_function_by_state(enums.STATE_ONE)

is not much different to

call_function_by_state('state_one')

Because enum keys are actually strings and the first example without syntax sugar looks like

call_function_by_state(enums['STATE_ONE'])

Plus Lua is not a statically typed language, you can’t refactor your enums. So not much point in doing it this way.

For performance Defold suggests to pass hashes instead of strings. Very valid, but straightforward way is calling the hash() function.

call_function_by_state(hash('state_one'))

My way is to automatically cache hashes in a global table

call_function_by_state(hashed.state_one)

hashed module can be found here https://github.com/Lerg/defold-hashed

I hope that helps.

4 Likes