I’m pretty new to this forum and I’m an amateur but I wanted to know if there was a way to tell if a value was changed inside of a table. I created a data store script and I have a looped save but I feel like I’m doing it wrong. So instead I wanted to only save the data if it was changed and not loop the saving.
Here’s what I’m working with right now:
> while wait(5) do
for i, player in pairs(game.Players:GetPlayers())do
_G.PlayerData[player.userId] = dataStore:GetAsync(player.userId, _G.PlayerData[player.userId])
updateEvent:FireClient(player, _G.PlayerData[player.userId])
end
end
I works and all but I’m not sure if it’ll eventually lag the server or any other good stuff. Like I said, I’m still an amateur. I appreciate you taking the time to check this, thank you.
local tab = {1,2,3}--your table
local proxy = newproxy(true)--the proxy userdata you will use for access and new values from now on
local meta = getmetatable(proxy)
meta.__index = tab
meta.__newindex = function(self,i,v1)
if tab[i]~=v1 then
tab[i]=v1
print("Changed")
end
end
proxy.test = 1 -->Changed
print(proxy.test) -->1
Shouldn’t you use rawset to avoid a C stack overflow, or does that not happen in the way you set it up? I’m thinking that because you have __newindex, when you set the value tab[i] = v1, this invokes metamethods as well. Even if you do a check with the not equal operator, it’s still getting called.
Data stores have an OnUpdate callback, where you can connect a function to the value whenever it changes, so you could just do something like this:
local dsConnections = {}
-- connects a callback to anyone already in the server
-- this is probably only needed for studio testing
for _, player in ipairs(game.Players:GetPlayers()) do
dsConnections[player.UserId] = dataStore:OnUpdate(player.UserId, function(dat)
updateEvent:FireClient(player, dat)
end)
end
-- connects a callback to any player who joins the server
game.Players.PlayerAdded:Connect(function(player)
-- There should never already be a callback connected before a player joins,
-- but it never hurts to be careful
local connection = dsConnections[player.UserId]
if connection then connection:Disconnect() end
dsConnections[player.UserId] = dataStore:OnUpdate(player.UserId, function(dat)
updateEvent:FireClient(player, dat)
end)
end)
-- disconnects the callback to any leaving player
game.Players.PlayerRemoving:Connect(function(player)
local connection = dsConnections[player.UserId]
if connection then connection:Disconnect() end
dsConnections[player.UserId] = nil
end)
Otherwise, with normal tables, the approach I go for is creating custom Set and Get methods for my table, and editing a storage table inside it using those functions. Then I could add custom functions inside the Set function, since this would (or is supposed to) be the only way to change data inside the Storage table:
local Module = {
Storage = {}
}
function Module:Get(i)
return self.Storage[i]
end
function Module:Set(i, v)
-- check if self.Storage[i] ~= v
-- if so, set a flag true, or fire a remote event
self.Storage[i] = v
end
This is how I like to do it, because it lets you track nearly everything the table is doing. You can replace the prints with whatever you want to do when that action happens.
Thank you to @nooneisback for helping me write this for Lua 5.1!
local function TableTracker(Table)
local proxy = newproxy(true); local meta = getmetatable(proxy)
meta.__index = function(_, key)
print(tostring(Table).." - access to element ".. tostring(key))
return Table[key]
end
meta.__newindex = function(_, key, value)
print(tostring(Table).." - update to element ".. tostring(key) .. ' to ' .. tostring(value))
Table[key] = value
end
meta.__len = function()
print(tostring(Table).." - access to table length")
return #Table
end
return proxy
end
Then, when you create the table you want to track, do
local tbl = TableTracker({"124","hagfal", "foo"})
and then it behaves like this:
tbl[2] = 4 -- prints "table:XXXXXXXX - update to element 2 to 4"
local var = tbl[1] -- prints "table:XXXXXXXX - access to element 1"
This works for dictionaries and arrays. When it prints element i it would print element key for dictionaries.
This is best way I know of to properly track a table.
local function TableTracker(Table)
local proxy = newproxy(true); local meta = getmetatable(proxy)
meta.__index = function(_, key)
print(tostring(Table).." - access to element ".. tostring(key))
return Table[key]
end
meta.__newindex = function(_, key, value)
print(tostring(Table).." - update to element ".. tostring(key) .. ' to ' .. tostring(value))
Table[key] = value
end
--had to remove pairs as it only supports tables
meta.__len = function()
print(tostring(Table).." - access to table length")
return #Table
end
return proxy
end
You could implement a replacement for __pairs as a custom method that returns the same thing, but I can’t really do much about large code chunks from where I am right now.
Hold up - are you using the userdata solely to proxy metamethods to update the table and create an update call? That’s an interesting way to use them. And I assume that raw methods are for if you set a metatable directly to a table and have other metamethods (e.g. __index and __newindex) involved.
That’s why the function to create them is called newproxy. They are just empty userdatas that you can do anything you want with, but that is what they are meant for. And as you said, rawset is used for if you don’t want to use indirect access, but a single table.
Basically empty userdatas. Their main use was the __len metamethod for the # operator, since tables don’t have access to it, as well as them being slightly faster and better at memory efficiency than empty tables when used for indirect access; however, that’s pretty much it.
Nope, you cannot. Userdatum is the most primitive object available. In vanilla Lua, it is nothing more than a reserved space in your memory that serves as an interface for Lua and C functions. A proxy is just a userdatum with an empty metatable.
In other words, you can use them if you don’t need to store any data, but do require metamethods.