Skip to content

Commit

Permalink
Merge pull request #10890 from nanaya/score-solo-index
Browse files Browse the repository at this point in the history
Use new score index for leaderboard
  • Loading branch information
peppy authored Jan 30, 2024
2 parents ff73b05 + 891ac19 commit dfbbc7c
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 469 deletions.
178 changes: 71 additions & 107 deletions app/Http/Controllers/BeatmapsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,66 @@ private static function baseScoreQuery(Beatmap $beatmap, $mode, $mods, $type = n
return $query;
}

private static function beatmapScores(string $id, ?string $scoreTransformerType, ?bool $isLegacy): array
{
$beatmap = Beatmap::findOrFail($id);
if ($beatmap->approved <= 0) {
return ['scores' => []];
}

$params = get_params(request()->all(), null, [
'limit:int',
'mode',
'mods:string[]',
'type:string',
], ['null_missing' => true]);

if ($params['mode'] !== null) {
$rulesetId = Beatmap::MODES[$params['mode']] ?? null;
if ($rulesetId === null) {
throw new InvariantException('invalid mode specified');
}
}
$rulesetId ??= $beatmap->playmode;
$mods = array_values(array_filter($params['mods'] ?? []));
$type = presence($params['type'], 'global');
$currentUser = \Auth::user();

static::assertSupporterOnlyOptions($currentUser, $type, $mods);

$esFetch = new BeatmapScores([
'beatmap_ids' => [$beatmap->getKey()],
'is_legacy' => $isLegacy,
'limit' => $params['limit'],
'mods' => $mods,
'ruleset_id' => $rulesetId,
'type' => $type,
'user' => $currentUser,
]);
$scores = $esFetch->all()->loadMissing(['beatmap', 'user.country', 'user.userProfileCustomization']);
$userScore = $esFetch->userBest();
$scoreTransformer = new ScoreTransformer($scoreTransformerType);

$results = [
'scores' => json_collection(
$scores,
$scoreTransformer,
static::DEFAULT_SCORE_INCLUDES
),
];

if (isset($userScore)) {
$results['user_score'] = [
'position' => $esFetch->rank($userScore),
'score' => json_item($userScore, $scoreTransformer, static::DEFAULT_SCORE_INCLUDES),
];
// TODO: remove this old camelCased json field
$results['userScore'] = $results['user_score'];
}

return $results;
}

public function __construct()
{
parent::__construct();
Expand Down Expand Up @@ -280,7 +340,7 @@ public function show($id)
/**
* Get Beatmap scores
*
* Returns the top scores for a beatmap
* Returns the top scores for a beatmap. Depending on user preferences, this may only show legacy scores.
*
* ---
*
Expand All @@ -296,60 +356,18 @@ public function show($id)
*/
public function scores($id)
{
$beatmap = Beatmap::findOrFail($id);
if ($beatmap->approved <= 0) {
return ['scores' => []];
}

$params = get_params(request()->all(), null, [
'limit:int',
'mode:string',
'mods:string[]',
'type:string',
], ['null_missing' => true]);

$mode = presence($params['mode']) ?? $beatmap->mode;
$mods = array_values(array_filter($params['mods'] ?? []));
$type = presence($params['type']) ?? 'global';
$currentUser = auth()->user();

static::assertSupporterOnlyOptions($currentUser, $type, $mods);

$query = static::baseScoreQuery($beatmap, $mode, $mods, $type);

if ($currentUser !== null) {
// own score shouldn't be filtered by visibleUsers()
$userScore = (clone $query)->where('user_id', $currentUser->user_id)->first();
}

$scoreTransformer = new ScoreTransformer();

$results = [
'scores' => json_collection(
$query->visibleUsers()->forListing($params['limit']),
$scoreTransformer,
static::DEFAULT_SCORE_INCLUDES
),
];

if (isset($userScore)) {
$results['user_score'] = [
'position' => $userScore->userRank(compact('type', 'mods')),
'score' => json_item($userScore, $scoreTransformer, static::DEFAULT_SCORE_INCLUDES),
];
// TODO: remove this old camelCased json field
$results['userScore'] = $results['user_score'];
}

return $results;
return static::beatmapScores(
$id,
null,
// TODO: change to imported name after merge with other PRs
\App\Libraries\Search\ScoreSearchParams::showLegacyForUser(\Auth::user()),
);
}

/**
* Get Beatmap scores (temp)
* Get Beatmap scores (non-legacy)
*
* Returns the top scores for a beatmap from newer client.
*
* This is a temporary endpoint.
* Returns the top scores for a beatmap.
*
* ---
*
Expand All @@ -359,68 +377,14 @@ public function scores($id)
*
* @urlParam beatmap integer required Id of the [Beatmap](#beatmap).
*
* @queryParam legacy_only Set to true to only return legacy scores. Example: 0
* @queryParam mode The [Ruleset](#ruleset) to get scores for.
* @queryParam mods An array of matching Mods, or none // TODO.
* @queryParam type Beatmap score ranking type // TODO.
*/
public function soloScores($id)
{
$beatmap = Beatmap::findOrFail($id);
if ($beatmap->approved <= 0) {
return ['scores' => []];
}

$params = get_params(request()->all(), null, [
'limit:int',
'mode',
'mods:string[]',
'type:string',
], ['null_missing' => true]);

if ($params['mode'] !== null) {
$rulesetId = Beatmap::MODES[$params['mode']] ?? null;
if ($rulesetId === null) {
throw new InvariantException('invalid mode specified');
}
}
$rulesetId ??= $beatmap->playmode;
$mods = array_values(array_filter($params['mods'] ?? []));
$type = presence($params['type'], 'global');
$currentUser = auth()->user();

static::assertSupporterOnlyOptions($currentUser, $type, $mods);

$esFetch = new BeatmapScores([
'beatmap_ids' => [$beatmap->getKey()],
'is_legacy' => false,
'limit' => $params['limit'],
'mods' => $mods,
'ruleset_id' => $rulesetId,
'type' => $type,
'user' => $currentUser,
]);
$scores = $esFetch->all()->loadMissing(['beatmap', 'user.country', 'user.userProfileCustomization']);
$userScore = $esFetch->userBest();
$scoreTransformer = new ScoreTransformer(ScoreTransformer::TYPE_SOLO);

$results = [
'scores' => json_collection(
$scores,
$scoreTransformer,
static::DEFAULT_SCORE_INCLUDES
),
];

if (isset($userScore)) {
$results['user_score'] = [
'position' => $esFetch->rank($userScore),
'score' => json_item($userScore, $scoreTransformer, static::DEFAULT_SCORE_INCLUDES),
];
// TODO: remove this old camelCased json field
$results['userScore'] = $results['user_score'];
}

return $results;
return static::beatmapScores($id, ScoreTransformer::TYPE_SOLO, null);
}

public function updateOwner($id)
Expand Down
10 changes: 6 additions & 4 deletions app/Libraries/Search/ScoreSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,20 @@ public function getQuery(): BoolQuery

$beforeTotalScore = $this->params->beforeTotalScore;
if ($beforeTotalScore === null && $this->params->beforeScore !== null) {
$beforeTotalScore = $this->params->beforeScore->isLegacy()
$beforeTotalScore = $this->params->isLegacy
? $this->params->beforeScore->legacy_total_score
: $this->params->beforeScore->total_score;
}
if ($beforeTotalScore !== null) {
$scoreQuery = (new BoolQuery())->shouldMatch(1);
$scoreField = $this->params->isLegacy ? 'legacy_total_score' : 'total_score';
$scoreQuery->should((new BoolQuery())->filter(['range' => [
'total_score' => ['gt' => $beforeTotalScore],
$scoreField => ['gt' => $beforeTotalScore],
]]));
if ($this->params->beforeScore !== null) {
$scoreQuery->should((new BoolQuery())
->filter(['range' => ['id' => ['lt' => $this->params->beforeScore->getKey()]]])
->filter(['term' => ['total_score' => $beforeTotalScore]]));
->filter(['term' => [$scoreField => $beforeTotalScore]]));
}

$query->must($scoreQuery);
Expand Down Expand Up @@ -142,7 +143,8 @@ private function addModsFilter(BoolQuery $query): void
$allMods = $this->params->rulesetId === null
? $modsHelper->allIds
: new Set(array_keys($modsHelper->mods[$this->params->rulesetId]));
$allMods->remove('PF', 'SD', 'MR');
// CL is currently considered a "preference" mod
$allMods->remove('CL', 'PF', 'SD', 'MR');

$allSearchMods = [];
foreach ($mods as $mod) {
Expand Down
33 changes: 28 additions & 5 deletions app/Libraries/Search/ScoreSearchParams.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,33 @@ public static function fromArray(array $rawParams): static
}

/**
* This returns value for isLegacy based on user preference
* This returns value for isLegacy based on user preference, request type, and `legacy_only` parameter
*/
public static function showLegacyForUser(?User $user): null | true
{
public static function showLegacyForUser(
?User $user = null,
?bool $legacyOnly = null,
?bool $isApiRequest = null
): null | true {
$isApiRequest ??= is_api_request();
// `null` is actual parameter value for the other two parameters so
// only try filling them up if not passed at all.
$argLen = func_num_args();
if ($argLen < 2) {
$legacyOnly = get_bool(Request('legacy_only'));

if ($argLen < 1) {
$user = \Auth::user();
}
}

if ($legacyOnly !== null) {
return $legacyOnly ? true : null;
}

if ($isApiRequest) {
return null;
}

return $user?->userProfileCustomization?->legacy_score_only ?? UserProfileCustomization::DEFAULT_LEGACY_ONLY_ATTRIBUTE
? true
: null;
Expand Down Expand Up @@ -93,9 +116,9 @@ public function setSort(?string $sort): void
{
switch ($sort) {
case 'score_desc':
$sortColumn = $this->isLegacy ? 'legacy_total_score' : 'total_score';
$this->sorts = [
new Sort('is_legacy', 'asc'),
new Sort('total_score', 'desc'),
new Sort($sortColumn, 'desc'),
new Sort('id', 'asc'),
];
break;
Expand Down
2 changes: 2 additions & 0 deletions database/factories/Solo/ScoreFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public function definition(): array

// depends on all other attributes
'data' => fn (array $attr): array => $this->makeData()($attr),

'legacy_total_score' => fn (array $attr): int => isset($attr['legacy_score_id']) ? $attr['total_score'] : 0,
];
}

Expand Down
Loading

0 comments on commit dfbbc7c

Please sign in to comment.