Do tweens made at each function firing take up memory?

I have a little notification system when a player spends money, there’s a “-$10” that floats up from their money UI. It’s done through a module script that’s fired every time a player’s money changes. I’ve noticed that the module script is using a lot of memory for some reason. I can’t figure out why, as this is the only thing the script does.

function NotificationSystem:MoneyNotify(player, amount)
	local playerGui = player:FindFirstChild("PlayerGui")
	local PlayerInfoScreen = playerGui.PlayerInfo
	local MoneyNotification = PlayerInfoScreen.MoneyNotification:Clone()
	MoneyNotification.Parent = PlayerInfoScreen
	MoneyNotification.Position = UDim2.new(math.random()/10,0,0.874,0)
	MoneyNotification.Text = "-"..amount.."₡"
	MoneyNotification.TextColor3 = UIConfig.NotificationConfig.NegativeMoneyColor
	MoneyNotification.TextStrokeColor3 = UIConfig.NotificationConfig.NegativeMoneyStrokeColor
	MoneyNotification.Visible = true
	local UpTween = TweenService:Create(MoneyNotification, MoneyUpTweenInfo, {Position = MoneyNotification.Position - UDim2.new(0,0,0.1,0)})
	local FadeTween = TweenService:Create(MoneyNotification, MoneyFadeTweenInfo, {TextTransparency = 1})
	UpTween:Play()
	wait(0.2)
	FadeTween:Play()
	wait(2)
	MoneyNotification:Destroy()
end

I’m wondering if creating a Tween every time NotificationSystem:MoneyNotify() is called from a local script is consuming unwanted memory. Is this a thing, and what are some things I can do to work around this?

1 Like

Yes, creating a tween costs performance, you should call it once and play it only in the module function if i am right.

3 Likes

Interesting if that’s the case then it makes sense why FPS games performance takes up a lot of memory. I know for a fact they use tween on a local script for the aim down sights on guns

Every function causes loss of performance right? Normally the amount would be to small to make an impact, but if you do it a hundred or even a thousand times it might be noticable like in this case.
This is just an assumption though

2 Likes

It’s really easy to test in your case.
Create the tween at the beginning of the script.
Only play it in your function.
See how your script affects the memory.

Then leave it that way, even if you don’t notice much of a difference. It’s just cleaner.

Also, why not clone MoneyNotification with all the properties you are setting already configured? (Text, TextColor, etc.) Even though it may take microseconds of time it all adds up.

Basically, try to do everything one time, then use it later on in the script instead of doing all the extra work.

1 Like

just cache tweens

local MoneyNotification = PlayerInfoScreen.MoneyNotification
local UpTween = TweenService:Create(MoneyNotification, MoneyUpTweenInfo, {Position = MoneyNotification.Position - UDim2.new(0,0,0.1,0)})
local FadeTween = TweenService:Create(MoneyNotification, MoneyFadeTweenInfo, {TextTransparency = 1})
local player = Players.LocalPlayer
	local playerGui = player:FindFirstChild("PlayerGui")
	local PlayerInfoScreen = playerGui.PlayerInfo

function NotificationSystem.MoneyNotify(amount:number):()


	MoneyNotification.Parent = PlayerInfoScreen
	MoneyNotification.Position = UDim2.new(math.random()/10,0,0.874,0)
	MoneyNotification.Text = "-"..amount.."₡"
	MoneyNotification.TextColor3 = UIConfig.NotificationConfig.NegativeMoneyColor
	MoneyNotification.TextStrokeColor3 = UIConfig.NotificationConfig.NegativeMoneyStrokeColor
	MoneyNotification.Visible = true

	UpTween:Play()
	task.wait(0.2)
	FadeTween:Play()
	task.wait(2)
	MoneyNotification.Parent=nil
end

Make sure to reset its properties tho

1 Like

As a general rule of thumb, anything that exists will cost memory, but tweens don’t take up a lot of memory. You don’t need to cache tweens, but it’s also fine if you do.

Judging by your code, it doesn’t seem like it should be leaking any memory, as all references are destroyed or runs out of scope. Most likely, the garbage collector isn’t “garbaging” because you haven’t reached the garbage collecting memory threshold yet.

However, it would be nice if you provided more data (such as a screenshot) so we could diagnose the problem further.

You are reusing the exact same tween, you do need to cache them.
See “You don’t need to cache,” and then the developer is going to cry, “Nooooo, my code sucks; I have to rescript it for the 663525235th time.”
All problems in gaming is started by developers like “i don’t need to”.
Nope, you do need to.

There’s no “you do” or “you don’t” in small cases like these. Tweens are extremely tiny, and thus it’s negligible whether you decide to cache them or not. It’s a semantic preference, and it makes no real-world difference in non-performance-critical code.

Many people prefer writing functions like these:

-->> long code
-->> long code
-->> long code

local function foo()
    local bar = 2
    print(bar)
end

Rather than:

local bar = 2

-->> long code
-->> long code
-->> long code

local function foo()
    print(bar)
end

Even though the former option is less performant, the difference is incredibly negligible. Some people would rather not scroll all the way up to figure out what a variable is. This is not bad practice, it’s just preference.

1 Like

I may not be an expert at memory stuff in programming, but I think there is no problem here. Lua uses a garbage collector, which means it automatically disposes memory of variables that reference to an object that is no longer referenced by a script. For example, making local variables inside of a code block, if it’s a one time thing that is only referenced inside that code block, it will most likely be handled by the garbage collector later.

Tho, it is a problem when the external variables are referenced by some function, like global variables in a script, since the garbage collector thinks that it could be referenced at any moment, so it doesn’t dispose it, same happens with variables in tables, tho tables can be disposed if they’re no longer referenced anymore, if you do have a table that is referenced many times after, but say you have some values inside of it that you’re no longer using or referencing, they remain there.

Roblox also uses garbage collectors for destroyed instances, which accounts for tweens too.

But garbage collectors don’t run instantly, instead they run in a cycle, so it can take some time to dispose them.

You can see more information in a previous post like this one:

How often is MonkeyNotify being called? Is it called within a thread or event callback?

It’s unlikely that those two tweens alone are causing memory to spike, especially since they should be collecting correctly. My theory is that if you’re calling this in an event callback, the yielding is invalidating the thread cache.

Yes, and you can get that back/remove that with tween:Destroy()
(use the name of the tween)

1 Like

I don’t think I can cache the tween because it’s with a different TextLabel every time. I clone the notification label each time because it’s one of those things where you can spam buy stuff so you might have 10 of the notifications at one time.

1 Like

you can make a hash dictionary for it
like:

local hashIn:{[TextLabel]:Tween} = {}
local hashFade:{[TextLabel]:Tween} = {}

and if not found hash tween then create one and put inside dictionary.

2 Likes

Since each label is only being tweened once, making a hash table to store tweens is not only redundant, but also costs performance and cause memory leaks if not cleared up properly.

OP’s original code was fine, no modifications are needed.

1 Like

“Memory leaks”
When last time you were destroying core part of your UI?
Also it can be easyly avoided by adding “k” __mode metatable anyways so your point is not valid.

1 Like

If you look at his original code, he clearly destroys the notification after fading:

Under the assumption that this is your typical disappearing notification, there is no reason you should be caching your tweens. What are you caching it for? You wouldn’t be using it again.

__mode only works with non-instances; from the documentation:

Clearly, both the key (text label) and value (tween) of your code are both instances, so it will never be garbage collected. Using mode on such tables adds yet another layer of redundancy to an already redundant datastructure.

1 Like

That an outdated information.
Go test it out yourself.
it is a weak value infact (at least when destroyed)

I’d be careful making blankets statements like that. Your initial solution by caching was already redundant, but now you’re in misinformation territory.

The documentation for __mode is not outdated. It doesn’t matter if the instance is destroyed or not. It is still a reference inside the table and will not be garbage collected. Again, __mode does not work with instances. If you want do test it out yourself, you can try running the following code:

local weak = setmetatable({}, {__mode = "k"})
local Part = Instance.new("Part")
weak[Part] = true

Part:Destroy()

while true do
	task.wait(2)
	print(weak) --> you will notice that "Part" is never garbage collected, despite destruction
end


Regardless, caching the tweens that will never be used again isn’t the correct solution. It’s extra code that runs without purpose.

2 Likes

Yeah i tested it in kv mode aswell and doesnt seem to get gced.
That strange…
Either it works for only some instances/specific conditions or i have a false memory dunno.