Better Way to Detect Changes In Tables

As a Roblox developer, it is currently too tedious to detect changes in a table. Currently, the only ways to detect changes in a table are these two methods.

First Method:

This method will have issues if you have things already defined in the original table since __newindex only triggers if the Value in the original table doesn’t exist.

local Sample, Changed = {}, Instance.new("BindableEvent")
local Table = setmetatable({}, {
     __index = Sample,
     __newindex = function(self, Key, Value)
          if Sample[Key] ~= Value then
                local OldValue = Sample[Key]
                Sample[Key] = Value
                Changed:Fire(Key, Value, OldValue)
          end
     end,
})

Changed.Event:Connect(function(Key, Value, OldValue)
      print(Key, Value, OldValue)
end)

Table.Name = "focasds"
Table.Name = "Make This A Thing"

Second Method:

local Table, Changed = {}, Instance.new("BindableEvent")
local function SetValue(Key, Value)
    if Table[Key] ~= Value then
        local OldValue = Table[Key]
        Table[Key] = Value
        Changed:Fire(Key, Value, OldValue)
    end
end

Changed.Event:Connect(function(Key, Value, OldValue)
      print(Key, Value, OldValue)
end)

SetValue("Name", "focasds")
SetValue("Name", "Make This A Thing")

:red_circle: Issue: There many occasions where we need to detect changes in a table to update other things that rely on a table, for example:

  • I have a custom object that has an Enabled property, when this one changes some code needs to run to make other changes. To avoid both methods mentioned, I am forced to just add a function named SetEnabled since is easier than all of that code, but I love following the style that the engine provides. (‘Enabled’ property for particles, ScreenGui, constraints, scripts, etc…)
  • I have a huge table with some info in it, this one can get changed at any time but there is no way to detect changes either unless I execute one of the two ways mentioned.
  • The methods mentioned in my opinion uses a bit more memory and kinda slow down things as well. (This third point is minimal and is not a big issue)

:green_circle: Proposed Solution:

First Solution: (My favorite)

A table.changed function which requires a Table and a Callback. When something gets added, changed, or removed from the Table the Callback should be called and pass the Key, Value, and the Previous Value before the change.

The function should be called no matter how things are changed in the table. In other words, changing normally, using rawset, and even things removed by the GC cycle in weak metatables should not affect table.changed.

local Table = {}
table.changed(Table, function(Key, Value, OldValue)
       print(Key, Value, OldValue)
end)

Second Solution:

A __changed metamethod which gets called when something gets added, changed, or removed from the Table the function in it should be called and pass the Key, Value, and the Previous Value before the change.

The function should be called no matter how things are changed in the table. In other words, changing normally, using rawset, and even things removed by the GC cycle in weak metatables should not affect the behavior of __changed.

local Table = setmetatable({}, {
    __changed = function(self, Key, Value, OldValue)
        print(Key, Value, OldValue)
    end,
})

If Roblox is able to address this issue, it would improve my development experience because I will be able to detect changes efficiently, write less code, it will help beginners, there will not be any need to use the two methods mentioned at the beginning of the post, and finally I will be able to detect changes in any table without being affected by rawset, already existing keys, and gc cycle.

Thank you.

15 Likes

We still don’t have custom objects in the engine unfortunately, so I think the least we could get for sure is a native method to track table changes that can be consistent across all potential implementations. Changed events are a pretty big part of pure Luau custom objects and I’ve seen Roblox writing them in developer-facing code as well, including the Lua Chat System.

I think for this case it might be best to submit a Luau RFC so that tracking table changes is available in the Luau language itself and not a Roblox-exclusive function. For example, if Roblox adopts your first proposed suggestion to have this as a member of the table standard library then any project that uses Luau can also take advantage of that functionality. It does break convention though so I think the metamethod proposal makes more sense here.

5 Likes

Im curious, since I agree with Colbert on the metamethod approach on this to keep with convention, should the logic around table.insert and related table methods not respecting metamethods still be true here?

I honestly have no idea, no matter how much I read this chunk of text:

I end up not understanding how to make a feature request for Luau in specific.

They should, as it would be nice to detect any change made in the table object.

RFC: Request for Comments - Wikipedia

The Luau code base doesn’t take “feature requests”, you can suggest new features as RFCs, and if your RFC is accepted then it is candidate for implementation.

The quoted screenshot explains exactly what you need to do to file it as an RFC.

I honestly have no idea on how to use GitHub, specially for making the desired request. I kinda followed the steps given but nothing shows up to type…

You can find a tutorial at GitHub flow - GitHub Docs

As a programmer you will be exposed to git/GitHub at some point or another so no better time to start self-learning than now.

1 Like

I’m sorry to say, but we are not going to implement this feature because of impact on performance and it not being implementable in various cases where metamethods are already ignored.

6 Likes

I honestly expected this because eventually, the interpreter will have to do some extra checks slowing down things, but currently detecting changes in Lua Tables is a bit of a pain. Thank you for responding!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.