Skip to content

Commit a908c6b

Browse files
committed
DO NOT MERGE, this is just a prototype as a base for discussion.
Progress towards #2525. The basic idea is to replace a lot of scattered scoring logic (which might become more complex) with encoding enough information in the rank cache. The information is encoded in a sort key that can be sorted descendingly. The sort key is a tuple (serialized as string into the database) of fixed precision decimals. Each tuple is using 9 decimals and left padded with `0`s, so it's sortable via normal DB query operations. If sorted ascending (e.g. penalty time), then we subtract from a very large number. For example, with ICPC scoring, the top 2 teams from NWERC 2024 gets these sort keys: ``` 00000000000000000000013.000000000,999999999999999999998725.000000000,999999999999999999999711.000000000 00000000000000000000011.000000000,999999999999999999998918.000000000,999999999999999999999701.000000000 ``` With this mechanism, we should be able to implement other planned scoring methods (in particular partial scoring, optimization problems) without too much complication on the business logic side. We do use `bcmath` with fixed precision to not run into numerical precision issues with sequencing floating point operations. This is not important today, but will become important once we deall with non-integers (e.g. for optimization problems). It also caches some computation and makes scoreboard computation more efficient today.
1 parent 21eb3a6 commit a908c6b

File tree

8 files changed

+309
-217
lines changed

8 files changed

+309
-217
lines changed

webapp/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"doctrine/orm": "^2.14",
6565
"eligrey/filesaver": "2.*",
6666
"enshrined/svg-sanitize": "^0.21.0",
67+
"ext-bcmath": "*",
6768
"fortawesome/font-awesome": "6.*",
6869
"friendsofsymfony/rest-bundle": "^3.5",
6970
"ircmaxell/password-compat": "*",
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20250309122806 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return 'Add sort keys to rankcache, allowing us to support different scoring functions efficiently and elegantly';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('ALTER TABLE rankcache ADD sort_key_public TEXT DEFAULT \'\' NOT NULL COMMENT \'Opaque sort key for public audience.\', ADD sort_key_restricted TEXT DEFAULT \'\' NOT NULL COMMENT \'Opaque sort key for restricted audience.\'');
24+
$this->addSql('CREATE INDEX sortKeyPublic ON rankcache (sort_key_public)');
25+
$this->addSql('CREATE INDEX sortKeyRestricted ON rankcache (sort_key_restricted)');
26+
}
27+
28+
public function down(Schema $schema): void
29+
{
30+
// this down() migration is auto-generated, please modify it to your needs
31+
$this->addSql('DROP INDEX sortKeyPublic ON rankcache');
32+
$this->addSql('DROP INDEX sortKeyRestricted ON rankcache');
33+
$this->addSql('ALTER TABLE rankcache DROP sort_key_public, DROP sort_key_restricted');
34+
}
35+
36+
public function isTransactional(): bool
37+
{
38+
return false;
39+
}
40+
}

webapp/src/Entity/RankCache.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php declare(strict_types=1);
22
namespace App\Entity;
33

4+
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
45
use Doctrine\ORM\Mapping as ORM;
56

67
/**
@@ -18,6 +19,8 @@
1819
#[ORM\Index(columns: ['cid', 'points_public', 'totaltime_public', 'totalruntime_public'], name: 'order_public')]
1920
#[ORM\Index(columns: ['cid'], name: 'cid')]
2021
#[ORM\Index(columns: ['teamid'], name: 'teamid')]
22+
#[ORM\Index(columns: ['sort_key_public'], name: 'sortKeyPublic')]
23+
#[ORM\Index(columns: ['sort_key_restricted'], name: 'sortKeyRestricted')]
2124
class RankCache
2225
{
2326
#[ORM\Column(options: [
@@ -62,6 +65,20 @@ class RankCache
6265
#[ORM\JoinColumn(name: 'teamid', referencedColumnName: 'teamid', onDelete: 'CASCADE')]
6366
private Team $team;
6467

68+
#[ORM\Column(
69+
type: 'text',
70+
length: AbstractMySQLPlatform::LENGTH_LIMIT_TEXT,
71+
options: ['comment' => 'Opaque sort key for public audience.', 'default' => '']
72+
)]
73+
private string $sortKeyPublic = '';
74+
75+
#[ORM\Column(
76+
type: 'text',
77+
length: AbstractMySQLPlatform::LENGTH_LIMIT_TEXT,
78+
options: ['comment' => 'Opaque sort key for restricted audience.', 'default' => '']
79+
)]
80+
private string $sortKeyRestricted = '';
81+
6582
public function setPointsRestricted(int $pointsRestricted): RankCache
6683
{
6784
$this->points_restricted = $pointsRestricted;
@@ -149,4 +166,26 @@ public function getTeam(): Team
149166
{
150167
return $this->team;
151168
}
169+
170+
public function setSortKeyPublic(string $sortKeyPublic): RankCache
171+
{
172+
$this->sortKeyPublic = $sortKeyPublic;
173+
return $this;
174+
}
175+
176+
public function getSortKeyPublic(): string
177+
{
178+
return $this->sortKeyPublic;
179+
}
180+
181+
public function setSortKeyRestricted(string $sortKeyRestricted): RankCache
182+
{
183+
$this->sortKeyRestricted = $sortKeyRestricted;
184+
return $this;
185+
}
186+
187+
public function getSortKeyRestricted(): string
188+
{
189+
return $this->sortKeyRestricted;
190+
}
152191
}

0 commit comments

Comments
 (0)