mirror of https://github.com/hykilpikonna/AquaDX
[O] Pre-compute ranking cache
parent
8cb7ff8ed4
commit
5b20cb316b
|
@ -22,6 +22,7 @@ import java.time.LocalDate
|
|||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.Lock
|
||||
import kotlin.reflect.KCallable
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
|
@ -189,6 +190,8 @@ fun Str.md5() = MD5.digest(toByteArray(Charsets.UTF_8)).toHexString()
|
|||
|
||||
// Coroutine
|
||||
suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() }
|
||||
fun <T> thread(block: () -> T) = Thread { block() }.apply { start() }
|
||||
fun <T> Lock.maybeLock(block: () -> T) = if (tryLock()) try { block() } finally { unlock() } else null
|
||||
|
||||
// Paths
|
||||
fun path(part1: Str, vararg parts: Str) = Path.of(part1, *parts)
|
||||
|
|
|
@ -4,7 +4,11 @@ import ext.*
|
|||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import jakarta.annotation.PostConstruct
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
@ -32,18 +36,41 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
|||
}
|
||||
|
||||
// Pair<time, List<Pair<should_hide, player>>>
|
||||
private var rankingCache: Pair<Long, List<Pair<Bool, GenericRankingPlayer>>> = 0L to emptyList()
|
||||
private val rankingCacheDuration = 240_000
|
||||
private var rankingCache: List<Pair<Bool, GenericRankingPlayer>> = emptyList()
|
||||
private var rankingCacheLock = ReentrantLock()
|
||||
@API("ranking")
|
||||
fun ranking(@RP token: String?): List<GenericRankingPlayer> {
|
||||
val time = millis()
|
||||
val tableName = when (name) { "mai2" -> "maimai2"; "chu3" -> "chusan"; else -> name }
|
||||
|
||||
// Check if ranking cache needs to be updated
|
||||
// Check cache validity
|
||||
if (rankingCache.isEmpty()) (500 - "Ranking is computing... please wait")
|
||||
|
||||
val reqUser = token?.let { us.jwt.auth(it) }?.let { u ->
|
||||
// Optimization: If the user is not banned, we don't need to process user information
|
||||
if (!u.ghostCard.rankingBanned && !u.cards.any { it.rankingBanned }) null
|
||||
else u
|
||||
}
|
||||
|
||||
// Read from cache if we just computed it less than duration ago
|
||||
// Shadow-ban: Do not show banned cards in the ranking except for the user who owns the card
|
||||
// TODO: pagination
|
||||
if (time - rankingCache.first > rankingCacheDuration) {
|
||||
rankingCache = time to us.em.createNativeQuery(
|
||||
"""
|
||||
return rankingCache.filter { !it.l || it.r.username == reqUser?.username }
|
||||
.mapIndexed { i, it -> it.r.apply { rank = i + 1 } }
|
||||
.also { logger.info("Ranking returned in ${millis() - time}ms") }
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
fun rakingCacheInit() = thread { rankingCacheRun() }
|
||||
|
||||
// Every 20 minutes
|
||||
@Scheduled(fixedRate = 20, timeUnit = TimeUnit.MINUTES)
|
||||
fun rankingCacheRun() = rankingCacheLock.maybeLock { rankingCacheCompute() }
|
||||
|
||||
fun rankingCacheCompute() {
|
||||
val time = millis()
|
||||
val tableName = when (name) { "mai2" -> "maimai2"; "chu3" -> "chusan"; else -> name }
|
||||
rankingCache = us.em.createNativeQuery(
|
||||
"""
|
||||
SELECT
|
||||
c.id,
|
||||
u.user_name,
|
||||
|
@ -61,31 +88,19 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
|||
GROUP BY p.user_id, u.player_rating
|
||||
ORDER BY u.player_rating DESC;
|
||||
"""
|
||||
).exec.mapIndexed { i, it ->
|
||||
it[7].truthy to GenericRankingPlayer(
|
||||
rank = i + 1,
|
||||
name = it[1].toString(),
|
||||
rating = it[2]!!.int,
|
||||
lastSeen = it[3].toString(),
|
||||
accuracy = it[4]!!.double,
|
||||
fullCombo = it[5]!!.int,
|
||||
allPerfect = it[6]!!.int,
|
||||
username = it[8]?.toString() ?: "user${it[0]}"
|
||||
)
|
||||
}
|
||||
).exec.mapIndexed { i, it ->
|
||||
it[7].truthy to GenericRankingPlayer(
|
||||
rank = i + 1,
|
||||
name = it[1].toString(),
|
||||
rating = it[2]!!.int,
|
||||
lastSeen = it[3].toString(),
|
||||
accuracy = it[4]!!.double,
|
||||
fullCombo = it[5]!!.int,
|
||||
allPerfect = it[6]!!.int,
|
||||
username = it[8]?.toString() ?: "user${it[0]}"
|
||||
)
|
||||
}
|
||||
|
||||
val reqUser = token?.let { us.jwt.auth(it) }?.let { u ->
|
||||
// Optimization: If the user is not banned, we don't need to process user information
|
||||
if (!u.ghostCard.rankingBanned && !u.cards.any { it.rankingBanned }) null
|
||||
else u
|
||||
}
|
||||
|
||||
// Read from cache if we just computed it less than duration ago
|
||||
// Shadow-ban: Do not show banned cards in the ranking except for the user who owns the card
|
||||
return rankingCache.r.filter { !it.l || it.r.username == reqUser?.username }
|
||||
.mapIndexed { i, it -> it.r.apply { rank = i + 1 } }
|
||||
.also { logger.info("Ranking computed in ${millis() - time}ms") }
|
||||
logger.info("Ranking computed in ${millis() - time}ms")
|
||||
}
|
||||
|
||||
@API("playlog")
|
||||
|
|
Loading…
Reference in New Issue