Making a function that detects if a table is indexed?

Idk if I wrote the title properly however what I’m asking is, let’s say I have a table:

local Table = {}

I wanna be able to detect when something like table.insert(Table, 123) is called. (On that table)

Is this possible?

can’t you just detect it being indexed the line after you insert something?
For example:

table.insert(Table, 123)
print("Inserted")

Use the __index metamethod lua-users wiki: Metamethods Tutorial

4 Likes

For checking when a table is indexed, you can use the __index metamethod.
For checking when a table index if written to, you can use the __newindex metamethod.

local tbl = setmetatable({},{
    __index = function()print"indexed"end,
    __newindex = function()print"newindexed"end
})
local _ = tbl[1] --> indexed
tbl[1] = 1 --> newindexed

Although, if there is a current index in the table, these events wont fire.

local tbl = setmetatable({1},{
    __index = function()print"indexed"end,
    __newindex = function()print"newindexed"end
})
local _ = tbl[1] -- nothing prints
tbl[1] = 1 -- nothing prints

In regards to table.insert, it uses raw sets and gets, so you will be unable to detect when it is inserted.

4 Likes

To force all indexes and new indexes to be accessed using the metamethod, it is common to make a new userdata using the Userdata newproxy(Bool hasMetatable) method. table.insert, rawset, and rawget don’t work on userdatas.

This isn’t how you’d normally go about things like this but you could run a loop and check the differences

local function Watch(t, filter, func)
    local prev = {}
    local diff = {}
    while true do
        diff = {} -- Clear old differences
        for pos, value in pairs(t) do
           if not prev[pos] == value then -- Check for discrepancies
              if (value == nil and filter == "Removed") or (prev[pos] == nil and filter == "Inserted") or (not filter) then
              diff[pos] = {Previous = prev[pos], New = value}
           end
           prev[pos] = value -- Update for next time
        end
        func(diff)
        game:GetService("RunService").Heartbeat:Wait()
    end
end

local myTable = {}
Watch(myTable, "Inserted", function(diff)
    for pos, data in pairs(diff) do
        print("Inserted "..data.New.." at "..pos)
    end
end)

Not tested but you get the idea

I did this

setmetatable(PlayerInventory.ItemInventory,{
    __newindex = function() DataStore.InventoryNotifcation() end
})

The notifcation function is set to print(“New index”)
However when I use table.insert() on the ItemInventory this does not fire.

As this could work I’m looking for a shorter way to do it preferably without loops

local RawTable = {}
local Table = setmetatable({}, {
	__index = function(_, Key)
		print("Read table with key: " .. tostring(Key))
		return RawTable[Key]
	end,
	
	__newindex = function(_, Key, Value)
		print("Write to table with key: " .. tostring(Key) .. " and value: " .. tostring(Value))
		RawTable[Key] = Value
	end,
})

Table.Test1 = 1
Table[1] = 123

print(Table.Test1)
print(Table.Test2)
print(Table[1])
1 Like

The insert method will still work on this table and the metamethod will not able able to detect it. Also something that should be mentioned that hasn’t is that the index metamethod only fires when an index is missing. Thus, userdatas prevent indexes from being added using insert / rawset and always ensure the metamethod fires.

1 Like

After reading your post, I think what you’re looking for is how to detect a new value being added to the table. But if you want to index tables or something then use the metatable method instead and ignore this.

a somewhat simplified solution

I have tested it and have come up with a solution , for detecting a new inserted value and it’s type , this does not utilize setmetatable .

It will detect whether an item has been inserted and then even tell you what type of value was inserted through such a simple way too !! (like boolean , string etc.)

Output :

image

proves to be convenient sometimes, to detect the type as well

 tablex = {}
   
local printed = false--some sort of debounce methodology

local default_amount_of_values = #tablex
  print("initial values are " .. default_amount_of_values)

  local change_detected=false

 function detect_change()
	 if change_detected==false and printed==false then  
	
	  local new_amount = table.getn(tablex)--get values passed on heartbeat
	     if new_amount>default_amount_of_values--difference detected
         then change_detected=true--meaning a change was detected
	     print("a change was detected")
      print((type(tablex[#tablex])) .." was added")--print the new value's type 
   printed=true
      change_detected=true

     end
   end
  end

local runService = game:GetService("RunService")
runService.Heartbeat:Connect(detect_change)--connect this to run on heartbeat

that all would detect a change, but we didn’t have anything added yet so let’s test it !
right below this line in my code :

 if change_detected==false and printed==false then

add this line below that line in my code :

 table.insert(tablex,"string")  

But since we used heartbeat it would fire nearly 400 times a second (or more)

so that’s why I implemented a debounce mechanism (printed==false ) etc.
and now it would only fire maximum about 2 times and will detect any change in the table.

working script, tested of course

Use this script anywhere, preferably in a server script, or run this in the Command bar and look at the Output

 tablex = {}
   
local printed = false

local default_amount_of_values = #tablex
  print("initial values are " .. default_amount_of_values)

  local change_detected=false

 function detect_change()
	 if change_detected==false and printed==false then  
	 table.insert(tablex,"string")  --we insert a  value for testing purposes..
	  local new_amount = table.getn(tablex)
	     if new_amount>default_amount_of_values
         then change_detected=true
	     print("a change was detected")
      print((type(tablex[#tablex])) .." was added")
   printed=true
      change_detected=true

     end
   end
  end

local runService = game:GetService("RunService")
runService.Heartbeat:Connect(detect_change)

Output :

image

1 Like

Exactly what I needed, Thank you.

1 Like