This commit is contained in:
lx
2026-07-06 14:01:11 -04:00
commit c4f97d729d
16468 changed files with 935321 additions and 0 deletions
@@ -0,0 +1,5 @@
{
"lint": {
"ImplicitReturn": "fatal"
}
}
@@ -0,0 +1,73 @@
local CorePackages = game:GetService("CorePackages")
local Cryo = require(CorePackages.Cryo)
local ArgCheck = require(CorePackages.ArgCheck)
local PromiseUtilities = require(CorePackages.AppTempCommon.LuaApp.PromiseUtilities)
local FetchSubdividedThumbnails = require(script.Parent.FetchSubdividedThumbnails)
local PerformFetch = require(CorePackages.AppTempCommon.LuaApp.Thunks.Networking.Util.PerformFetch)
local ICON_PAGE_COUNT = 100
local ICON_SIZE = "150x150"
local function convertToId(value)
return tostring(value)
end
local ApiFetchThumbnails = {}
local keyMapper = function (request)
local targetId = request.targetId
local size = request.iconSize and "."..request.iconSize or ""
local requestName = request.requestName and "."..request.requestName or ""
return "luaapp.thumbnails." .. convertToId(targetId)..size..requestName
end
ApiFetchThumbnails.KeyMapper = keyMapper
local function subdivideIdsArray(requests, limit)
local someTokens = {}
for i = 1, #requests, limit do
local subArray = Cryo.List.getRange(requests, i, i + limit - 1)
table.insert(someTokens, subArray)
end
return someTokens
end
function ApiFetchThumbnails.Fetch(networkImpl, targetIds, imageSize, requestName, fetchFunction, storeDispatch, store)
local size = imageSize or ICON_SIZE
ArgCheck.isType(targetIds, "table", "targetIds")
ArgCheck.isType(requestName, "string", "requestName")
ArgCheck.isNonNegativeNumber(#targetIds, "targetIds count")
local requests = {}
local promises = {}
-- Filter out the icons that are already in the store.
for _, targetId in pairs(targetIds) do
table.insert(requests, {
targetId = targetId,
iconSize = size,
})
end
local subdividedRequestsArray = subdivideIdsArray(requests, ICON_PAGE_COUNT)
for _, subdividedRequests in ipairs(subdividedRequestsArray) do
table.insert(
promises,
store:dispatch(FetchSubdividedThumbnails.Fetch(
networkImpl,
subdividedRequests,
keyMapper,
requestName,
fetchFunction,
storeDispatch
))
)
end
return PromiseUtilities.Batch(promises)
end
function ApiFetchThumbnails.GetFetchingStatus(state, targetId, iconSize, requestName)
return PerformFetch.GetStatus(state, keyMapper({targetId = targetId, requestName = requestName, iconSize = iconSize}))
end
return ApiFetchThumbnails
@@ -0,0 +1,139 @@
local CorePackages = game:GetService("CorePackages")
local LuaApp = CorePackages.AppTempCommon.LuaApp
local ArgCheck = require(CorePackages.ArgCheck)
local Thumbnail = require(LuaApp.Models.Thumbnail)
local PerformFetch = require(LuaApp.Thunks.Networking.Util.PerformFetch)
local Promise = require(LuaApp.Promise)
local Result = require(LuaApp.Result)
local TableUtilities = require(LuaApp.TableUtilities)
local RETRY_MAX_COUNT = math.max(0, settings():GetFVariable("LuaAppNonFinalThumbnailMaxRetries"))
local RETRY_TIME_MULTIPLIER = math.max(0, settings():GetFVariable("LuaAppThumbnailsApiRetryTimeMultiplier")) -- seconds
local FetchSubdividedThumbnails = {}
function FetchSubdividedThumbnails._fetchIcons(store, networkImpl, targetIds, iconSize, keyMapper, requestName, fetchFunction, storeDispatch)
local function keyMapperForCurrentRequestNameAndSize(targetId)
return keyMapper({
targetId = targetId,
requestName = requestName,
iconSize = iconSize
})
end
local function getTableOfFailedResults(failedTargetIds)
local results = {}
for _, targetId in pairs(failedTargetIds) do
local key = keyMapperForCurrentRequestNameAndSize(targetId)
results[key] = Result.new(false, {
targetId = targetId,
})
end
return results
end
return fetchFunction(networkImpl, targetIds, iconSize):andThen(
function(result)
local results = getTableOfFailedResults(targetIds)
local validIcons = {}
local data = result and result.responseBody and result.responseBody.data
if typeof(data) == "table" then
for _, iconInfo in pairs(data) do
if Thumbnail.isCompleteThumbnailData(iconInfo) then
local targetId = tostring(iconInfo.targetId)
local success = false
if Thumbnail.checkStateIsFinal(iconInfo.state) then
validIcons[targetId] = Thumbnail.fromThumbnailData(iconInfo, iconSize)
success = true
end
results[keyMapperForCurrentRequestNameAndSize(targetId)] = Result.new(success, iconInfo)
end
end
end
store:dispatch(storeDispatch(validIcons))
return Promise.resolve(results)
end,
function(err)
local results = getTableOfFailedResults(targetIds)
return Promise.resolve(results)
end
)
end
function FetchSubdividedThumbnails._fetch(store, networkImpl, targetIds, size, keyMapper, requestName, fetchFunction, storeDispatch)
return FetchSubdividedThumbnails._fetchIcons(store, networkImpl, targetIds, size, keyMapper, requestName, fetchFunction, storeDispatch)
:andThen(function(results)
local completedIcons = {}
local iconResults = results
if _G.__TESTEZ_RUNNING_TEST__ then
RETRY_MAX_COUNT = 1
RETRY_TIME_MULTIPLIER = 0.001
end
local function retry(retryCount)
local remainingUnfinalizedIcons = {}
for k, result in pairs(iconResults) do
local isSuccessful, iconInfo = result:unwrap()
-- Retry icon request for targetId that failed.
if isSuccessful and Thumbnail.checkStateIsFinal(iconInfo.state) then
completedIcons[k] = result
else
table.insert(remainingUnfinalizedIcons, iconInfo)
end
end
if TableUtilities.FieldCount(remainingUnfinalizedIcons) == 0 then
--All requests are successful
return Promise.resolve(completedIcons)
end
local delayPromise = Promise.new(function(resolve, reject)
coroutine.wrap(function()
wait(RETRY_TIME_MULTIPLIER * math.pow(2, retryCount - 1))
resolve()
end)()
end)
return delayPromise:andThen(function()
return FetchSubdividedThumbnails._fetchIcons(store, networkImpl,
targetIds, size, keyMapper, requestName, fetchFunction, storeDispatch)
end):andThen(function(newResults)
iconResults = newResults
if retryCount > 1 then
return retry(retryCount - 1)
else
return Promise.resolve(completedIcons)
end
end)
end
return retry(RETRY_MAX_COUNT)
end)
end
function FetchSubdividedThumbnails.Fetch(networkImpl, requests, keyMapper, requestName, fetchFunction, storeDispatch)
ArgCheck.isType(requests, "table", "requests")
ArgCheck.isType(requestName, "string", "requestName")
ArgCheck.isNonNegativeNumber(#requests, "requests count")
FetchSubdividedThumbnails.KeyMapper = keyMapper
return PerformFetch.Batch(requests, keyMapper, function(store, filteredrequests)
local targetIdsNeeded = {}
local size
-- Filter out the icons that are already in the store.
for _, request in ipairs(filteredrequests) do
local targetId = request.targetId
size = request.iconSize
table.insert(targetIdsNeeded, targetId)
end
return FetchSubdividedThumbnails._fetch(store, networkImpl, targetIdsNeeded, size, keyMapper, requestName, fetchFunction, storeDispatch)
end)
end
return FetchSubdividedThumbnails
@@ -0,0 +1,10 @@
-- Helper function to throttle based on player Id:
return function(throttle, userId)
assert(type(throttle) == "number")
assert(type(userId) == "number")
-- Determine userRollout using last two digits of user ID:
-- (+1 to change range from 0-99 to 1-100 as 0 is off, 100 is full on):
local userRollout = (userId % 100) + 1
return userRollout <= throttle
end
@@ -0,0 +1,67 @@
return function()
local ThrottleUserId = require(script.Parent.ThrottleUserId)
describe("ThrottleUserId", function()
it("should always reject zero%", function()
local gating = ThrottleUserId(0, 10000)
expect(gating).to.equal(false)
gating = ThrottleUserId(0, 10001)
expect(gating).to.equal(false)
gating = ThrottleUserId(0, 10025)
expect(gating).to.equal(false)
gating = ThrottleUserId(0, 10075)
expect(gating).to.equal(false)
gating = ThrottleUserId(0, 10099)
expect(gating).to.equal(false)
gating = ThrottleUserId(0, 10100)
expect(gating).to.equal(false)
end)
it("should always accept 100%", function()
local gating = ThrottleUserId(100, 10000)
expect(gating).to.equal(true)
gating = ThrottleUserId(100, 10001)
expect(gating).to.equal(true)
gating = ThrottleUserId(100, 10025)
expect(gating).to.equal(true)
gating = ThrottleUserId(100, 10075)
expect(gating).to.equal(true)
gating = ThrottleUserId(100, 10099)
expect(gating).to.equal(true)
gating = ThrottleUserId(100, 10100)
expect(gating).to.equal(true)
end)
it("should reject IDs over throttle percent", function()
local gating = ThrottleUserId(25, 10050)
expect(gating).to.equal(false)
gating = ThrottleUserId(50, 10075)
expect(gating).to.equal(false)
gating = ThrottleUserId(75, 10099)
expect(gating).to.equal(false)
end)
it("should accept IDs under throttle percent", function()
local gating = ThrottleUserId(1, 10100)
expect(gating).to.equal(true)
gating = ThrottleUserId(10, 10109)
expect(gating).to.equal(true)
gating = ThrottleUserId(25, 10023)
expect(gating).to.equal(true)
end)
end)
end