GetPropertyChangedSignal should include the new value in the callback

As a developer, it is too hard to not write redundant code with GetPropertyChangedSignal.

With GetPropertyChangedSignal, there aren’t very many scenarios where you wouldn’t want to know what the property actually changed to. For example, if I want to write code that does something when the player’s team changes, I have to write it like this.

LocalPlayer:GetPropertyChangedSignal("Team"):Connect(function()
    local team = LocalPlayer.Team
    --code
end)

I’d much rather simplify this to be…

LocalPlayer:GetPropertyChangedSignal("Team"):Connect(function(team)
    --code
end)
78 Likes

I’d prefer it to include the new and the old value, much like StateChanged from Humanoid.

So more like…

LocalPlayer:GetPropertyChangedSignal("Team"):Connect(function(old, new)
    print(LocalPlayer,"switched from team", old, "to team", new)
end)
44 Likes

Bumping this thread since I really would appreciate this

If I want to do something like

part:GetPropertyChangedSignal("Position"):connect(function()

end) 

In order to get the last and new position I have to keep track of the change myself, which is pretty annoying

2 Likes

I believe it was implemented like this because serializing the value for Lua could be non-trivial and slow down the signal significantly for cases that don’t need it.

4 Likes

Although the creation of functions with extra upvalues can also be expensive (at least the first time).

This would make development a whole lot more convenient

2 Likes

Since there hasn’t been any official statement on this, I too would enjoy this feature. This feature is valid & would allow me to clean up unneeded code & streamline my development.

3 Likes

When implementing this function we specifically decided to err on the side of performance over usability since it was originally implemented primarily for performance reasons. There are some cases where the value isn’t required so it’s best to avoid having to pay the cost of marshalling the value through Lua-C++ boundary. An example use case where the value wouldn’t be required is if you didn’t want to update something immediately but instead wanted to mark that it has changed and update it later to buffer property changes together and avoid unnecessary updates.

12 Likes

This is a really disappointing thing to hear as a developer. I would much rather throw away any performance benefits to provide the value being passed w/the connected function.

With attributes on the horizon, there is API that is very similar to GetPropertyChangedSignal called GetAttributeChangedSignal. Now there’s a big difference between doing instance.Property vs making a call to get an attribute with :GetAttribute(). I shouldn’t have to do some funny work arounds just to pass the changed value in my function if I’m literally going to index/call the value I need.

I’ve been writing attribute-based code & I had to do a big refactor to make it work w/ValueBase instances & using .Changed w/the value is significantly better UX for me. Having to throw a call at the top of the function to get the value of the specified property/attribute is really tedious & provides some really useless code bloat that can be fixed by simply passing the changed value.

I don’t expect to do GetPropertyChangedSignal and not provide that value when .Changed provides the value. This behavior should definitely be re-evaluated because people will be moving from doing .Changed to GetAttributeChangedSignal when attributes release, making converting code bases from ValueBases to attributes all the more tedious.

Please re-evaluate these methods.

33 Likes

I don’t know anything about the Roblox codebase so forgive my naiivety, but something that comes to mind is that callbacks passed are immutable, as are their arguments. It seems reasonable enough for me that you could statically infer if a connection expects any arguments, and only run the expensive code then.

15 Likes

There’s a lot of optimization potential for lazy serialization. Imagine if Terrain:ReadVoxels only serialized the occupancy table if the resulting value is used.

I think it would also be useful for the instance itself to be passed, so that many instances can be connected in parallel without creating new functions for each instance. Ideally, lazy serialization would also apply to function arguments that are never used, so that cases where there are two arguments (and only the second one is used) can still benefit.

4 Likes

I couldn’t support this more. My main drive towards creating custom signal systems was specifically to pass on the respective instance with the event call, in an effort to avoid creating a function for each instance.

The fact that it is even technically possible to achieve this (I had no clue that lazy serialization was a thing) is an even stronger argument towards adding this feature.

4 Likes

Bump.

When working with GetAttributeChangedSignal, having to do item:GetAttribute("AttributeName") after each GetAttributeChangedSignal event still feels a bit lumpy, as does having to write local value = item.Value after each event.

I usually use these specific ChangedSignals to immediately check or obtain the value of the value that had changed, and if that value was simply provided by default instead of needing an extra line to get that value, which disrupts workflow, it would make it more streamlined.

I was honestly surprised when I just learned that this feature wasn’t a thing in the first place. It just makes sense for it to return the value of what was changed.

7 Likes

It’s a little silly it works this way. I don’t think I’ve ever used GetPropertyChangedSignal/GetAttributeChangedSignal without immediately needing the changed value.

Why not have 2 functions for that

GetPropertyChangedSignal - No Values
GetPropertyChangedSignalWithValues - Values Included (Previous, Current)

I agree!

Even though this is an old request, it makes sense even more so now that Attributes have been popularized.

local MyPart: BasePart

local function KillStatusChanged(kill_active: boolean?)
    print((kill_active and `The part should kill when touched!`) or `The part should no longer kill when touched!`)
end

MyPart:GetAttributeChangedSignal("ShouldKill"):Connect(KillStatusChanged)

This would make state machines so much quicker! Of course you can :GetAttribute, but passing the new value directly into the function would cut down on that sort of request.

Having to get the attribute every time its updated is rather redundant.

I still think this idea works considering debug.info(f, "a") tells you how many arguments a function takes.