Hello, i need feedback on my Animation Cache System so i can see what needs to improve/change thanks.
AnimationService.__index = AnimationService
local Players = game:GetService("Players")
AnimationService._cache = setmetatable({}, {__mode="k"})
AnimationService._globalCache = {}
AnimationService.Debug = true
local TrackWrapper = {}
TrackWrapper.__index = TrackWrapper
local function debugLog(...)
if AnimationService.Debug then
print("[AnimationService]", ...)
end
end
function TrackWrapper.new(track, character, animId, cacheTable)
local self = setmetatable({}, TrackWrapper)
self._track = track
self._character = character
self._animId = animId
self._cacheTable = cacheTable
self._priority = track.Priority
self._isPlaying = false
self._conn = track.Stopped:Connect(function()
self._isPlaying = false
debugLog("Animation stopped:", animId, "for", character.Name)
end)
return self
end
function TrackWrapper:Play(fadeTime, weight, speed)
fadeTime = fadeTime or 0.1
weight = weight or 1
speed = speed or 1
self._track.Priority = self._priority
self._track:AdjustSpeed(speed)
self._track:Play(fadeTime)
self._track:AdjustWeight(weight, fadeTime)
self._isPlaying = true
if AnimationService.Debug then
print(string.format("[DEBUG][Play] %s playing for %s | Speed: %.2f | Weight: %.2f | Fade: %.2f",
self._animId, self._character.Name, speed, weight, fadeTime))
end
end
function TrackWrapper:Stop(fadeTime)
if self._track then
self._track:Stop(fadeTime or 0.1)
end
self._isPlaying = false
debugLog(self._animId, "stopped for", self._character.Name, "| Fade:", fadeTime or 0.1)
end
function TrackWrapper:Destroy()
if self._conn then
self._conn:Disconnect()
self._conn = nil
end
self._cacheTable[self._animId] = nil
if self._track then
self._track:Destroy()
self._track = nil
end
self._isPlaying = false
if AnimationService.Debug then
print("[DEBUG][Destroy] Wrapper destroyed for", self._animId)
end
end
function TrackWrapper:__index(key)
local track = rawget(self, "_track")
if TrackWrapper[key] then
return TrackWrapper[key]
end
if track and typeof(track[key]) == "function" then
return function(_, ...)
return track[key](track, ...)
end
end
if track and track[key] ~= nil then
return track[key]
end
return rawget(TrackWrapper, key)
end
local function GetAnimator(Character)
if not Character then return nil end
local Humanoid = Character:FindFirstChildOfClass("Humanoid")
if Humanoid then
local Animator = Humanoid:FindFirstChildOfClass("Animator")
if not Animator then
Animator = Instance.new("Animator")
Animator.Parent = Humanoid
end
return Animator
end
local AnimationController = Character:FindFirstChildOfClass("AnimationController")
if AnimationController then
local Animator = AnimationController:FindFirstChildOfClass("Animator")
if not Animator then
Animator = Instance.new("Animator")
Animator.Parent = AnimationController
end
return Animator
end
return nil
end
local function GetTrack(animId, Animator, AnimationInstance)
if not AnimationService._globalCache[animId] then
AnimationService._globalCache[animId] = {
instance = AnimationInstance,
_lastUsed = os.clock(),
}
if AnimationService.Debug then
print("[DEBUG][GlobalCache] Added animation:", animId)
end
else
AnimationService._globalCache[animId]._lastUsed = os.clock()
if AnimationService.Debug then
print("[DEBUG][GlobalCache] Reusing animation:", animId)
end
end
return Animator:LoadAnimation(AnimationService._globalCache[animId].instance)
end
local function countKeys(tbl)
local count = 0
for _ in pairs(tbl) do
count += 1
end
return count
end
task.spawn(function()
while task.wait(600) do
local now = os.clock()
local keys = {}
for animId in pairs(AnimationService._globalCache) do
table.insert(keys, animId)
end
local BATCH_SIZE = 20
for i = 1, #keys, BATCH_SIZE do
for j = i, math.min(i + BATCH_SIZE - 1, #keys) do
local animId = keys[j]
local data = AnimationService._globalCache[animId]
if data._lastUsed and (now - data._lastUsed > 1200) then
AnimationService._globalCache[animId] = nil
if AnimationService.Debug then
print("[DEBUG][GlobalCache] Removed unused animation:", animId)
end
end
end
task.wait(0.05)
end
end
end)
function AnimationService.new(Character, AnimationInstance, Priority, Looped, Speed, FadeTime)
if not Character or not AnimationInstance then return end
local Animator = GetAnimator(Character)
if not Animator then return end
local animId = AnimationInstance.AnimationId
AnimationService._cache[Character] = AnimationService._cache[Character] or {}
local charCache = AnimationService._cache[Character]
local Wrapper = charCache[animId]
if not Wrapper then
local Track = GetTrack(animId, Animator, AnimationInstance)
if Looped ~= nil then Track.Looped = Looped end
Wrapper = TrackWrapper.new(Track, Character, animId, charCache)
charCache[animId] = Wrapper
if AnimationService.Debug then
print("[DEBUG][CharacterCache] Added track for character:", Character.Name, animId)
end
else
if Priority then
Wrapper._priority = Priority
end
if AnimationService.Debug then
print("[DEBUG][CharacterCache] Reusing track for character:", Character.Name, animId)
end
end
Wrapper:Play(FadeTime, nil, Speed)
if AnimationService.Debug then
print(string.format("[DEBUG] Player '%s' animation cache size: %d | Global cache size: %d",
Character.Name,
countKeys(charCache),
countKeys(AnimationService._globalCache)))
end
return Wrapper
end
function AnimationService.StopAnimation(Character, AnimationInstance, FadeTime)
if not Character or not AnimationInstance then return end
local animId = AnimationInstance.AnimationId
local charCache = AnimationService._cache[Character]
if charCache and charCache[animId] then
charCache[animId]:Stop(FadeTime)
else
local Animator = GetAnimator(Character)
if not Animator then return end
for _, track in ipairs(Animator:GetPlayingAnimationTracks()) do
if track.Animation == AnimationInstance then
track:Stop(FadeTime or 0.1)
return
end
end
end
end
function AnimationService.StopAll(Character, FadeTime)
if not Character then return end
local charCache = AnimationService._cache[Character]
if charCache then
for _, wrapper in pairs(charCache) do
wrapper:Stop(FadeTime or 0.1)
end
end
local Animator = GetAnimator(Character)
if Animator then
for _, track in ipairs(Animator:GetPlayingAnimationTracks()) do
track:Stop(FadeTime)
end
end
end
Players.PlayerRemoving:Connect(function(player)
local char = player.Character
if char then
local charCache = AnimationService._cache[char]
if charCache then
for _, wrapper in pairs(charCache) do
wrapper:Destroy()
end
end
AnimationService._cache[char] = nil
end
end)
workspace.ChildRemoved:Connect(function(child)
local charCache = AnimationService._cache[child]
if charCache then
for _, wrapper in pairs(charCache) do
wrapper:Destroy()
end
AnimationService._cache[child] = nil
end
end)
return AnimationService