Animation Cache System Issue

Hello, i made a Animation Cache System about 2 months ago and there was been issues happening weeks later and when i play Animations Using the Cache system Sometimes it dosent not play at all, then it plays the second time and so on. And sometimes the Animation glitches First time play but when i first made it i didnt encounter such issues and Yes i preloaded and played the animations on a dummy and stopped it. I would appreciate the Help Thanks!

AnimationService.__index = AnimationService

AnimationService._cache = setmetatable({}, {__mode="k"})
AnimationService._globalCache = {}

AnimationService.Debug = true

local TrackWrapper = {}
TrackWrapper.__index = TrackWrapper

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
	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)
	fadeTime = fadeTime or 0
	if self._track then
		self._track:Stop(fadeTime)
	end
	self:Destroy()
	self._isPlaying = false

	if AnimationService.Debug then
		print(string.format("[DEBUG][Stop] %s stopped for %s | Fade: %.2f",
			self._animId, self._character.Name, fadeTime))
	end
end

function TrackWrapper:Destroy()
	if self._conn then
		self._conn:Disconnect()
		self._conn = nil
	end
	if self._cacheTable then
		self._cacheTable[self._animId] = nil
	end
	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 then
		warn("Wheres the characr")
	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()
				return
			end
		end
	end
end

function AnimationService.StopFolderAnimations(Character, Folder, FadeTime)
	
	local charCache = AnimationService._cache[Character]

	
	if charCache then
		for animId, wrapper in pairs(charCache) do
			if wrapper._track.Animation:IsDescendantOf(Folder) then
				wrapper:Stop(FadeTime)
			end
		end
	end

	local Animator = GetAnimator(Character)
	if Animator then
		for _, track in ipairs(Animator:GetPlayingAnimationTracks()) do
			if track.Animation:IsDescendantOf(Folder) then
				track:Stop(FadeTime or 0.1)
			end
		end
	end
end


function AnimationService.StopAll(Character, FadeTime)
	if not Character then return end
	local charCache = AnimationService._cache[Character]
	if charCache then
		for animId, wrapper in pairs(charCache) do
			wrapper:Stop(FadeTime)
		end
	end
	local Animator = GetAnimator(Character)
	if Animator then
		for _, track in ipairs(Animator:GetPlayingAnimationTracks()) do
			track:Stop(FadeTime or 0.1)
		end
	end
	AnimationService._cache[Character] = nil
end

local Players = game:GetService("Players")
Players.PlayerRemoving:Connect(function(player)
	local char = player.Character
	if char then
		AnimationService._cache[char] = nil
	end
end)

game:GetService("Workspace").ChildRemoved:Connect(function(child)
	if AnimationService._cache[child] then
		AnimationService._cache[child] = nil
	end
end)

return AnimationService```

so that fixes the Animation Playing issue?

Just play tracks loaded by Animator
That it.
Use dictionary if you need caching.
All can be done in 2 lines.

1 Like

no, that just means that the script is completely useless, and you can simplify it in so much ways

you really dont need all of this just to play a animation, it makes things hard to read and confusing

also i think a cause of issue u have is because ur destroying the animation when u stop it

function TrackWrapper:Destroy()
    if self._track then -- remove this whole bit 
		self._track:Destroy()
		self._track = nil
	end
end
2 Likes

Should i be using Cache system or just use GC?

… what?
Use a plain dictionary… you don’t need some bloated libraries to perform such a simple task.
Not like you need them for complex tasks either…

1 Like

the question was quite simple. should he use the notorious cache type or escape the sandbox and directly used the vm gc itself? amazing

1 Like

what do u mean bro i asked if i should be using Cache system or just use GC?

conceited ahh code

()

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.