Did you know that using Object Values you can store, load, and play animations from everywhere in your game? But how?
Setting the ObjectValue.Value property, the loaded animation can be accessed from anywhere in the game. This is useful if your animations aren’t loaded, and they look glitchy, so you can just do a for loop and play all of them, so they won’t look glitchy anymore. This was a bug fix for my problem.
I wrote a quick function that does all of this, you just need to specify the Animator, Animation, ObjectValue:
-- should be called once, not tested though
-- meant to be used in a ModuleScript
function LoadAnimation(Animator, Animation, ObjectValue)
local Track = Animator:LoadAnimation(Animation)
ObjectValue.Value = Track
This is an example of using this function.
LoadAnimation(Animator,Animation,ObjectValue) -- loading the animation
Did you also know that there is a maximum number of AnimationTracks that can be loaded onto Animators and that this is bordering on bad practice? You’re keeping 2n idle instances and consuming a decent amount of memory by loading tracks this way.
I’m assuming that you meant “should only be called once” as in the function in the tutorial and then every other time you should be using the reference held in the ObjectValue if you want to play or use the animation in the future. It has little to do with that; reread what I posted.
Each Animator has a maximum number of animation tracks that can be loaded (via LoadAnimation) on it, the number is in the 200s range or 200 itself. That gives way to produce problem number one which is eagerly loading too many animation tracks at one time. An animation-heavy experience may need to use more animations, few of which are played frequently, so you’d be consuming the limited capacity of loaded animations per Animator by “preloading” them all.
The second problem is that, eager loading. Animations consume memory to be loaded onto Humanoids, so when you’re calling load animation its downloading all the keyframe data and caching it onto the Animator object. Keeping that in mind, this pattern implicitly encourages poor memory management practice by letting unused animation data hang around to solve a trivial problem. It doesn’t get as bad as keeping unused sounds in the DataModel (a blank baseplate with Standard animation sets is around 0.7MB while sounds can go up to several hundred MB or a GB) but the main issue I wanted to highlight there is poor practice rather than the specific numbers.
Strictly depends on the context and the timing. Preloading is generally always useful but it can also become a huge noob trap (case in point: very common for novice developers to preload their entire experience which is the same as preloading nothing).
You are right in that unused instances can (and should be, anyway) discarded at a later point in time. The point of my posts - or if unclear, then clarifying now - is that this can become a noob trap. Developers will assume that they can just load all their animations and store them into ObjectValues and call it a day. Your client memory consumption won’t take kindly to that.
I would more strongly recommend holding AnimationTracks in a table so you aren’t generating an instance per to keep hold of them. Additionally you need to be wary that there’s a maximum number of loaded (or playing?) tracks per Animator so you can’t just “preload everything”. You should only be loading what you actually need at the time. For example, you can have basic movement animations loaded at all times but action animations should be on a case-by-case basis.
One would likely have to use a table regardless, as ObjectValues notoriously do not keep Instances in memory. So, if the AnimationTrack is never referenced elsewhere, it’ll eventually be picked up by garbage collection. Thereafter, the .Value property will evaluate to nil.
Modules are interesting, because they run only once, and then their output is cached for subsequent requires. The implication of this, is that objects output by modules will persist in memory, even when they have no references elsewhere. Seemingly, these cached objects are only ever deleted when the returning module is picked up by garbage collection (and, of course, there exists no other remaining strong references to them anywhere else).
By default, both keys and values of tables will never be picked up by garbage collection, so long as a strong reference to that table is maintained. (I say ‘by default’, as through alteration of the __mode metamethod, tables can be made capable of holding weak references. However, given that Instances, in particular, cannot be weakly referenced through these means, that’s an irrelevant aside here.)
I’ve actually (for quite some time) been planning on writing a full tutorial thread about memory management on Roblox. It’s a surprisingly rigorous endeavor, as much of my findings rely on making various black-box observations about Roblox’s implementation of less documented features. So if you’re ever looking for a more in-depth understanding, that’ll be out ‘eventually’. I’ve been putting it off, as in approaching some of these more abstract topics, there’s quite a bit of fundamental ground to cover. But rest assured, if the table can be accessed through usual means, chances are it’s being strongly referenced, and it shouldn’t be picked up by garbage collection.
I would be very interested in such a tutorial. Thank you for your in depth explanation. Currently I’m storing a giant player cache in module script. I can easily change it of course, but knowing ahead of time that down the road it will break will save me so much time.
Fingers crossed on the modulescript data not being reset for now. Lol I’m sitting here typing and you’re editing your comment. I’ve never seen a forum update live like that before. Kinda cool.
Anyway, would be really interested in that writeup if and whenever you get the chance. Best wishes in your gamedev journey.