AsyncCache - Fetch and keep!

This module fulfill this pattern:

  1. Yield to get info
  2. Cache got Info if not cleared
  3. Clear info if you need (Unyielded handler data that was returned will not be cached)

P.S: if load same key, then old info will be auto cleared (don’t worry, no memory leaks)

Example to get player thumbnail:

local PlayersService = game:GetService("Players")
local AsyncCache = require(script.AsyncCache)
local ThumbnailsCache = AsyncCache.new(function(player) -- handler that should return async data
	while (true) do
		local success, thumbnailId = pcall(function()
			return PlayersService:GetUserThumbnailAsync(player.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size352x352)
		end)

		if (not player.Parent) then break end
		if (success and thumbnailId) then
			return thumbnailId
		end

		task.wait(0.15)
	end
end)

PlayersService.PlayerAdded:Connect(function(player)
	ThumbnailsCache:Load(player) -- load data
	
	print(ThumbnailsCache:Get(player)) -- nil, not loaded yet (nil because this is sync get)
	print(ThumbnailsCache:IsLoading(player)) -- true
	
	local thumbnailId = select(2, ThumbnailsCache:GetAsync(player):await())
	-- selects only second argument, first argument is status

	print(thumbnailId) -- rbx://..
	print(ThumbnailsCache:Get(player)) -- same as above
end)

PlayersService.PlayerRemoving:Connect(function(player)
	ThumbnailsCache:Clear(player) --clear cached data
end)

You can get module from here: Async Cache - Roblox

4 Likes

Looks pretty cool nice work.
Would like to see some use cases and it in action.

1 Like

Looks absolutely amazing! Can you share benchmarks demonstrating how this speeds up loading? Would you mind also adding the possibility to add a TTL (time to live) and TTI (time to idle)? Thank you!

1 Like

This isn’t speed ups loading, instead it cache value from load and then you can get it. It only do what it should, I can add ttl, but what is time to idle mean? Is it something like timeout for request?

The point of a cache is to speed up getting data, hence it being stored in memory in a cache. A time to idle is a way of deleting keys when they haven’t been read or written to in a certain amount of time.

1 Like

Uh, I thought I already showed example (this may be use case). Just think about this like this:

  1. Load Info
  2. Cache It
  3. Now you can get info
  4. Clear info if you need
  5. You can repeat first step to last step (auto info clear will be called on load if key has data)

Cool thanks for showing me(character limit)

I understand now, I will add it soon

1 Like

Not really the best example as GetUserThumbnailAsync just returns a formatted string, it doesn’t make any web request to get the image.

PlayersService:GetUserThumbnailAsync() sends web request to roblox, why you think so?

Because it returns a string to the player’s thumbnail in rbxthumb:// format, if that does make a web request, Roblox are doing something wrong as it has all the information it needs without a web request.

1 Like

Anyway, this function is async, so it yields. I understand that this link maybe static, but I thought about this from programm point of view. Maybe I will change example later, but this not so matter

if you read the documentation, you’ll see it also returns a “bool describing if the image is ready to be used or not”.

AsyncCache V2.0
Added:

  • CacheObject (Object that keep your cached data, loads it and etc)
  • TTL (Time to live - basically this is time to keep your data before clean)
  • TTI (Time to idle - basically if you didn’t read cached data, then it will be cleared after time)
  • CacheObject:GetStatus() (Returns status of CacheObject (Empty/Loading/Loaded))
  • AsyncCache:GetCacheObject()
  • Option argument (More about it below)

Changed:

  • AsyncCache is more like wrapper above CacheObjects
  • AsyncCache.new(name, handler, globalOptions?) - new signature of method
  • AsyncCache:Load(key, options, otherArguments) - returns CacheObject or creates new if not exist
  • AsyncCacheObject keep cache objects inside it
  • AsyncCacheObject:Remove(key) clears CacheObject and removes key (:Clear() not removes key)

Okay, now let’s talk about some things.

AsyncCache:Method(key, otherArguments) call just do something like: AsyncCache:GetCacheObject(key):Method(otherArguments)

Below is signature of options (same for global options):

local options = {
	["TTL"] = { -- or number
		["Time"] = number,
		["Reload"] = true -- true for reload, function for callback(cacheObject)
	},
	["TTI"] = { -- or number
		["Time"] = number,
		["Reload"] = true -- true for reload, function for callback(cacheObject)
	}
}
}

options will override globalOptions.
options is for CacheObject:Load(options, otherArguments).
globalOptions is for AsyncCache.new(name, globalOptions)

New examples are below:

P.S: For examples below Imagine that :IsInGroup() method always returns true

TTL
local PlayersService = game:GetService("Players")
local AsyncCacheObject = require(script.AsyncCache).new("IsInGroupCache", function(player)
	task.wait(0.15) --test purpose
	return player:IsInGroup(14416268) --Yields
end)

PlayersService.PlayerAdded:Connect(function(player)
	local CacheObject = AsyncCacheObject:Load(player, {
		["TTL"] = {
			["Time"] = 3,
			["Reload"] = true -- or function(CacheObject, key)
		},
	})	
	
	local isInGroup = select(2, CacheObject:GetAsync():await())
	print(isInGroup) -- true
	task.wait(3)
	print(CacheObject:Get(player)) -- nil (because of TTL) TTL = Time To Live, basically it is just auto clear data after time
	
	-- Let's try AsyncCacheObject to call CacheObject methods!
	
	isInGroup = select(2, AsyncCacheObject:GetAsync(player):await()) 
	print(isInGroup) -- true (because we had set ["Reload"] = true in options for TTL)
	task.wait(3)
	print(AsyncCacheObject:Get(player)) --nil (again, because of TTL)
end)

PlayersService.PlayerRemoving:Connect(function(player)
	AsyncCacheObject:Remove(player) --[[
	call's CacheObject:Clear() and removes key, like: 
 	AsyncCacheObject["CacheObjects"][key] = nil
  	]]
end)
TTI
local PlayersService = game:GetService("Players")
local AsyncCacheObject = require(script.AsyncCache).new("IsInGroupCache", function(player)
	task.wait(0.15) --test purpose
	return player:IsInGroup(14416268) --Yields
end)

PlayersService.PlayerAdded:Connect(function(player)
	local CacheObject = AsyncCacheObject:Load(player, {
		["TTI"] = {
			["Time"] = 3,
			["Reload"] = true -- or function(CacheObject, key)
		},
	})	
	
	local isInGroup = select(2, CacheObject:GetAsync():await())
	task.wait(2)
	print(CacheObject:Get()) -- true
	task.wait(2)
	print(CacheObject:Get()) -- true
	task.wait(3.1)
	print(CacheObject:Get()) -- nil because of TTI (Time to idle)
	
	print(select(2, CacheObject:GetAsync():await())) -- true, because Reload property in TTI is set to true
end)

PlayersService.PlayerRemoving:Connect(function(player)
	AsyncCacheObject:Remove(player) --[[
	call's CacheObject:Clear() and removes key, like: 
 	AsyncCacheObject["CacheObjects"][key] = nil
  	]]
end)

I hope you will like it, there’s shouldn’t be errors, but anyway I will fix them if they exist.
You can get module from here: Link

Do you have features to add on your mind? Let me know

2 Likes