fix(score): preload beatmap when creating score token
This commit is contained in:
@@ -50,7 +50,17 @@ from app.storage.local import LocalStorageService
|
||||
|
||||
from .router import router
|
||||
|
||||
from fastapi import Body, Depends, Form, HTTPException, Path, Query, Request, Security
|
||||
from fastapi import (
|
||||
BackgroundTasks,
|
||||
Body,
|
||||
Depends,
|
||||
Form,
|
||||
HTTPException,
|
||||
Path,
|
||||
Query,
|
||||
Request,
|
||||
Security,
|
||||
)
|
||||
from fastapi.responses import FileResponse, RedirectResponse
|
||||
from httpx import HTTPError
|
||||
from pydantic import BaseModel
|
||||
@@ -99,6 +109,7 @@ async def submit_score(
|
||||
# 智能预取beatmap缓存(异步进行,不阻塞主流程)
|
||||
try:
|
||||
from app.service.beatmap_cache_service import get_beatmap_cache_service
|
||||
|
||||
cache_service = get_beatmap_cache_service(redis, fetcher)
|
||||
await cache_service.smart_preload_for_score(beatmap)
|
||||
except Exception as e:
|
||||
@@ -167,6 +178,34 @@ async def submit_score(
|
||||
return resp
|
||||
|
||||
|
||||
async def _preload_beatmap_for_pp_calculation(beatmap_id: int) -> None:
|
||||
"""
|
||||
预缓存beatmap文件以加速PP计算
|
||||
当玩家开始游玩时异步预加载beatmap原始文件到Redis缓存
|
||||
"""
|
||||
# 检查是否启用了beatmap预加载功能
|
||||
if not settings.enable_beatmap_preload:
|
||||
return
|
||||
|
||||
try:
|
||||
# 异步获取fetcher和redis连接
|
||||
fetcher = await get_fetcher()
|
||||
redis = get_redis()
|
||||
|
||||
# 检查是否已经缓存,避免重复下载
|
||||
cache_key = f"beatmap:raw:{beatmap_id}"
|
||||
if await redis.exists(cache_key):
|
||||
logger.debug(f"Beatmap {beatmap_id} already cached, skipping preload")
|
||||
return
|
||||
|
||||
await fetcher.get_or_fetch_beatmap_raw(redis, beatmap_id)
|
||||
logger.debug(f"Successfully preloaded beatmap {beatmap_id} for PP calculation")
|
||||
|
||||
except Exception as e:
|
||||
# 预缓存失败不应该影响正常游戏流程
|
||||
logger.warning(f"Failed to preload beatmap {beatmap_id}: {e}")
|
||||
|
||||
|
||||
class BeatmapScores(BaseModel):
|
||||
scores: list[ScoreResp]
|
||||
user_score: BeatmapUserScore | None = None
|
||||
@@ -311,6 +350,7 @@ async def get_user_all_beatmap_scores(
|
||||
description="**客户端专属**\n为指定谱面创建一次性的成绩提交令牌。",
|
||||
)
|
||||
async def create_solo_score(
|
||||
background_task: BackgroundTasks,
|
||||
beatmap_id: int = Path(description="谱面 ID"),
|
||||
version_hash: str = Form("", description="游戏版本哈希"),
|
||||
beatmap_hash: str = Form(description="谱面文件哈希"),
|
||||
@@ -319,6 +359,7 @@ async def create_solo_score(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
assert current_user.id is not None
|
||||
background_task.add_task(_preload_beatmap_for_pp_calculation, beatmap_id)
|
||||
async with db:
|
||||
score_token = ScoreToken(
|
||||
user_id=current_user.id,
|
||||
@@ -360,6 +401,7 @@ async def submit_solo_score(
|
||||
description="**客户端专属**\n为房间游玩项目创建成绩提交令牌。",
|
||||
)
|
||||
async def create_playlist_score(
|
||||
background_task: BackgroundTasks,
|
||||
room_id: int,
|
||||
playlist_id: int,
|
||||
beatmap_id: int = Form(description="谱面 ID"),
|
||||
@@ -415,7 +457,7 @@ async def create_playlist_score(
|
||||
status_code=400, detail="Playlist item has already been played"
|
||||
)
|
||||
# 这里应该不用验证mod了吧。。。
|
||||
|
||||
background_task.add_task(_preload_beatmap_for_pp_calculation, beatmap_id)
|
||||
score_token = ScoreToken(
|
||||
user_id=current_user.id,
|
||||
beatmap_id=beatmap_id,
|
||||
|
||||
@@ -223,8 +223,8 @@ class SpectatorHub(Hub[StoreClientState]):
|
||||
)
|
||||
logger.info(f"[SpectatorHub] {client.user_id} began playing {state.beatmap_id}")
|
||||
|
||||
# 预缓存beatmap文件以加速后续PP计算
|
||||
await self._preload_beatmap_for_pp_calculation(state.beatmap_id)
|
||||
# # 预缓存beatmap文件以加速后续PP计算
|
||||
# await self._preload_beatmap_for_pp_calculation(state.beatmap_id)
|
||||
|
||||
await self.broadcast_group_call(
|
||||
self.group_id(user_id),
|
||||
@@ -450,50 +450,3 @@ class SpectatorHub(Hub[StoreClientState]):
|
||||
if (target_client := self.get_client_by_id(str(target_id))) is not None:
|
||||
await self.call_noblock(target_client, "UserEndedWatching", user_id)
|
||||
logger.info(f"[SpectatorHub] {user_id} ended watching {target_id}")
|
||||
|
||||
async def _preload_beatmap_for_pp_calculation(self, beatmap_id: int) -> None:
|
||||
"""
|
||||
预缓存beatmap文件以加速PP计算
|
||||
当玩家开始游玩时异步预加载beatmap原始文件到Redis缓存
|
||||
"""
|
||||
# 检查是否启用了beatmap预加载功能
|
||||
if not settings.enable_beatmap_preload:
|
||||
return
|
||||
|
||||
try:
|
||||
# 异步获取fetcher和redis连接
|
||||
from app.dependencies.database import get_redis
|
||||
from app.dependencies.fetcher import get_fetcher
|
||||
|
||||
fetcher = get_fetcher()
|
||||
redis = get_redis()
|
||||
|
||||
# 检查是否已经缓存,避免重复下载
|
||||
cache_key = f"beatmap:raw:{beatmap_id}"
|
||||
if await redis.exists(cache_key):
|
||||
logger.debug(f"Beatmap {beatmap_id} already cached, skipping preload")
|
||||
return
|
||||
|
||||
# 在后台异步预缓存beatmap文件,存储任务引用防止被回收
|
||||
task = asyncio.create_task(
|
||||
self._fetch_beatmap_background(fetcher, redis, beatmap_id)
|
||||
)
|
||||
# 任务完成后自动清理,避免内存泄漏
|
||||
task.add_done_callback(lambda t: None)
|
||||
|
||||
except Exception as e:
|
||||
# 预缓存失败不应该影响正常游戏流程
|
||||
logger.warning(f"Failed to preload beatmap {beatmap_id}: {e}")
|
||||
|
||||
async def _fetch_beatmap_background(self, fetcher, redis, beatmap_id: int) -> None:
|
||||
"""
|
||||
后台获取beatmap文件
|
||||
"""
|
||||
try:
|
||||
# 使用fetcher的get_or_fetch_beatmap_raw方法预缓存
|
||||
await fetcher.get_or_fetch_beatmap_raw(redis, beatmap_id)
|
||||
logger.debug(
|
||||
f"Successfully preloaded beatmap {beatmap_id} for PP calculation"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to preload beatmap {beatmap_id}: {e}")
|
||||
|
||||
Reference in New Issue
Block a user