perf: use lookup maps instead of linear opponent scans in standings widgets#7649
perf: use lookup maps instead of linear opponent scans in standings widgets#7649Rathoz wants to merge 1 commit into
Conversation
90dff66 to
40a92a9
Compare
hjpalpha
left a comment
There was a problem hiding this comment.
lgtm on phone
fwiw it will make salts goal to allow A/B teams stuff harder
I'm currently convinced we'd need to support them inside Opponent.toName anyways (as other components, i.e. TeamParticipants use that), so the effect of this is probably not that bad. |
…idgets Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
40a92a9 to
6b7d775
Compare
There was a problem hiding this comment.
Pull request overview
Performance-focused refactor of the Swiss and FFA standings widgets to avoid repeated linear scans over round.opponents during rendering by precomputing per-round lookup maps keyed by opponent identity.
Changes:
- Build
entryByRoundonce per render: an array of{ [OpponentKey] = entry }maps per round. - Replace
Array.find(... Opponent.same ...)per-cell scans with O(1)entryByRound[columnIndex][slotKey]lookups. - Hoist opponent key computation out of per-column closures (compute once per row).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| lua/wikis/commons/Widget/Standings/Swiss.lua | Precomputes per-round opponent→entry maps and uses them for per-cell match lookup in Swiss standings rows. |
| lua/wikis/commons/Widget/Standings/Ffa.lua | Precomputes per-round opponent→entry maps and uses them for per-cell point/status lookup in FFA standings rows. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| local entryByRound = Array.map(standings.rounds, function(round) | ||
| local byName = {} | ||
| Array.forEach(round.opponents, function(entry) | ||
| byName[Opponent.toName(entry.opponent)] = entry | ||
| end) | ||
| return byName | ||
| end) |
| ---@return Renderable | ||
| function Helpers.createRow(standings, slot) | ||
| function Helpers.createRow(standings, slot, entryByRound) | ||
| local slotName = Opponent.toName(slot.opponent) |
| local entryByRound = Array.map(standings.rounds, function(round) | ||
| local byName = {} | ||
| Array.forEach(round.opponents, function(entry) | ||
| byName[Opponent.toName(entry.opponent)] = entry | ||
| end) | ||
| return byName | ||
| end) |
| function Helpers._createRoundBody(standings, round) | ||
| function Helpers._createRoundBody(standings, round, entryByRound) | ||
| return TableWidgets.TableBody{children = Array.map(round.opponents, function (slot) | ||
| local slotName = Opponent.toName(slot.opponent) |
Summary
Opponent.sameinside a per-cell nested loop to find an opponent's entry in each column round, producing O(R²·N²)Opponent.samecalls per render. The Swiss widget had the same pattern at O(R·N²).Opponent.sameis expensive: it can invoke uncachedmw.ext.TeamTemplate.rawPHP callbacks for team opponents, or allocate and sort arrays for party opponents.entryByRoundtable once per standings render: an array (one entry per round) of{ [Opponent.toName(entry.opponent)] = entry }maps.Opponent.toNamereturns a stable identity string (no PHP calls, no allocation). The per-cell lookup becomes a plain O(1) table access, andOpponent.toName(slot.opponent)is hoisted out of each row's per-column closure so it is computed once per row rather than once per column.toNamekeys are consistent across rounds — no fallback needed for entry lookups.Array.indexOf(match.opponents, …)call inside the Swiss per-cell body (which identifies the opposing player in a 2-opponent match) is deliberately left onOpponent.same: those opponents come from match2 records where renamed-team handling inOpponent.samematters, and it is only ever called on 2 opponents per match, so it is not a performance concern.How did you test this change?
busted --run=ci(full suite): 605 successes / 0 failures / 0 errors — includingspec/standings_model_spec.lua, which renders both the FFA and Swiss widgets end-to-end and asserts row ordering and cell content.luacheckon both modified files: 0 warnings / 0 errors.standings-tests-ai(PR test: add integration tests for standings #7647), which adds the widget render tests that lock the behavior being preserved here.🤖 Generated with Claude Code