" invalid key to 'next' "

Trying to loop through a dictionary and destroy values which are too far from the player, but for some reason it sometimes randomly errors with

invalid key to ‘next’

Looked around and seems like this error is caused because I’m removing values from the dictionary at the same time I’m looping through it which causes it to get confused.

for position, chunk in pairs(Chunks) do
    local Distance = (chunk.PositionValue.Value - Vector3.new(Camera.CFrame.X,0,Camera.CFrame.Z)).magnitude
    if Distance > 700 then
    	chunk:Destroy()
    	Chunks[position] = nil
    	wait()
    end
end

Any way to go around this error?

1 Like

This happens because you are modifying the table while iterating over it. You could build a list of Chunks to remove while iterating over the table and then remove them all at the end.
Alternatively, if you change this to an Array rather than a dictionary you can iterate over it in reverse order and safely modify it while iterating over it.

9 Likes

Extending on TheGamer101’s reply, here’s an example of how we’d go about doing this:

local fooValues = {
	foo1 = "bar",
	foo2 = "bar",
	foo3 = "baz",
	foo4 = "bar",
	foo5 = "baz",
}
local removedFoos = {}

-- add all keys with value of "baz" to removedFoos.
for foo, value in pairs(fooValues) do
	if value == "baz" then
		table.insert(removedFoos, foo)
	end
end
-- now that we're finished iterating over the table, we can remove its elements.
for _, removedFoo in ipairs(removedFoos) do
	fooValues[removedFoo] = nil
end
-- check if the elements were removed.
for foo, value in pairs(fooValues) do
	print(foo, value) --> foo2 bar foo1 bar foo4 bar
end
1 Like

The official Lua manual explicitly mentions that you should be fine doing that (see below). It’s possible you using pairs is causing unforseen issues, or that Roblox has otherwise modified next/pairs though. Either way, try using next, Chunks instead of pairs(Chunks) to see if that fixes the issue.

From the Lua manual

next (table [, index])

Allows a program to traverse all fields of a table. Its first argument is a table and its second argument is an index in this table. next returns the next index of the table and its associated value. When called with nil as its second argument, next returns an initial index and its associated value. When called with the last index, or with nil in an empty table, next returns nil . If the second argument is absent, then it is interpreted as nil . In particular, you can use next(t) to check whether a table is empty.

The order in which the indices are enumerated is not specified, even for numeric indices . (To traverse a table in numeric order, use a numerical for or the ipairs.)

The behavior of next is undefined if, during the traversal, you assign any value to a non-existent field in the table. You may however modify existing fields. In particular, you may clear existing fields.

3 Likes

It’s completely safe to remove (or change) entries from a table as you iterate over it. What’s not safe is adding new entries.
Do you happen to add new entries to the Chunks table somewhere else while this loop is yielding (during the wait)? That invalidates iterator states of iterators on that table (due to reallocations etc.) and can cause that error (or other odd behavior, such as iterating elements multiple times).

2 Likes

Yes entries are being added at the same time.

That would be the problem then. Wait until one or the other is done. :slight_smile:

table.removeing elements from an array during iteration should also be safe, then?

table.remove moves all of the indices above the provided one down, so I wouldn’t because you’ll get some weird undefined behavior.

2 Likes

The Lua spec states that you can change existing keys in a table while iterating through it (especially to nil.)
It’s happening because you are yielding (“wait”-ing) while iterating over a table. You likely have another script that adds new keys to the table during this yield.

You should add a “break” after the “wait()”, then start the loop over, like this:

repeat
	local changed = false
	local CameraCFrame = Camera.CFrame
	local CameraPosition = Vector3.new(CameraCFrame.X, 0, CameraCFrame.Z)
	for position, chunk in pairs(Chunks) do
		local Distance = (chunk.PositionValue.Value - CameraPosition).magnitude
		if Distance > 700 then
			chunk:Destroy()
			Chunks[position] = nil
			wait()
			changed = true
			break
		end
	end
until not changed

It may check the same chunks more than initially intended, but it should work.

I went in-depth on how to optimize LoD systems in a reply to this thread (although I just realized I never followed up on questions):
https://devforum.roblox.com/t/how-would-one-go-about-making-a-lod-system/29511/9