app: order the GPU list by performance (usable, discrete before integrated, rate in 5 MH/s buckets, memory)

the project lead, 6 October 2026: on PC 1 the integrated card sat between the 5090 and the 9070 XT. The list now shows
usable cards first, ordered by the measured rate since the start in 5 MH/s buckets (no flicker), discrete,
external and Apple before integrated, then memory; removed and unusable cards last; ties keep the detection
order. The row signature follows the order, so a reorder re-renders. Three UI tests. For 0.3.12.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-06 07:23:25 +00:00
parent f8088c49c3
commit 85e7e8e527
2 changed files with 34 additions and 2 deletions

View file

@ -258,7 +258,21 @@ var View = (function () {
function shortHex(h, head, tail) { h = String(h || ''); head = head || 8; tail = tail || 6; return h.length > head + tail + 2 ? h.slice(0, head) + '…' + h.slice(-tail) : h; }
var kindWord = Notices.kindWord;
// hot-plug (src/hotplug.rs): a removed card's row hides after five minutes (gone); a faulty one has no switch
function shownCards(cards) { return (cards || []).filter(function (c) { return !c.gone; }); }
// The list is ordered by performance (the project lead, 6 October 2026): usable cards first, then by the measured rate since the
// start (5 MH/s buckets so the order does not flicker), then discrete, external and Apple before integrated, then
// memory; removed and unusable cards last. Ties keep the detection order.
function perfRank(c) {
var usable = !(c.removed_at > 0) && !c.problem ? 0 : 1;
var integrated = c.kind === 'integrated' ? 1 : 0;
var bucket = -Math.floor(((c.enabled ? (c.hash_avg || c.hash_now || 0) : 0)) / 5);
return [usable, integrated, bucket, -(c.vram_mb || 0)];
}
function shownCards(cards) {
return (cards || []).map(function (c, i) { return { c: c, i: i, r: perfRank(c) }; }).sort(function (a, b) {
for (var k = 0; k < a.r.length; k++) { if (a.r[k] !== b.r[k]) return a.r[k] - b.r[k]; }
return a.i - b.i;
}).map(function (x) { return x.c; }).filter(function (c) { return !c.gone; });
}
// listed, driver fine, not unplugged: a worker can run on it
function present(c) { return !(c.removed_at > 0) && !c.problem; }
// the tooltip on a card's name: what the tool calls it (gfx1201), its device index, the OpenCL platform, the PCI address
@ -919,7 +933,7 @@ if (typeof document !== 'undefined') (function () {
if (s.detecting) { det.hidden = false; $('detect-text').textContent = 'asking the graphics cards to report in'; $('btn-cards-next').disabled = true; $('cards-sub').textContent = 'Asking the graphics cards to report in.'; cardsRendered = ''; return; }
det.hidden = true;
var cards = shownCards(s.mining.cards);
var sig = cards.map(function (c) { return c.key + ':' + (c.problem || '') + ':' + (c.removed_at > 0 ? 'r' : '') + ':' + (c.enabled ? 'on' : 'off'); }).join('|');
var sig = cards.map(function (c) { return c.key + ':' + (c.problem || '') + ':' + (c.removed_at > 0 ? 'r' : '') + ':' + (c.enabled ? 'on' : 'off'); }).join('|'); // cards is already in performance order, so a reorder changes the signature
if (sig !== cardsRendered) { cardsRendered = sig; renderCardRows(list, cards); }
if (!cards.length) {
$('cards-sub').textContent = 'No GPU this app can drive was found.';

View file

@ -172,6 +172,24 @@ test('hot-plug (src/hotplug.rs): a removed card and a faulty card are shown as s
// a row that is gone (five minutes after removal) is not shown at all; the big button counts only present cards
assert.deepEqual(V.shownCards([card(), card({ key: 'x', gone: true })]).map((c) => c.key), ['nvidia:0:RTX 5090']);
assert.equal(V.present(card()), true);
// performance order (6 October 2026): PC 1 detects 5090, integrated AMD, 9070 XT; the list shows the 5090, the 9070 XT,
// then the integrated card, whatever the detection order and whatever the integrated card's rate
const pc1 = [
card({ key: 'nvidia:0:RTX 5090', hash_avg: 122 }),
card({ key: 'amd:gfx1036', name: 'AMD Radeon(TM) Graphics', vendor: 'amd', kind: 'integrated', vram_mb: 512, hash_avg: 2.1, enabled: true }),
card({ key: 'amd:gfx1201', name: 'AMD Radeon RX 9070 XT', vendor: 'amd', kind: 'discrete', vram_mb: 16384, hash_avg: 18.2 }),
];
assert.deepEqual(V.shownCards(pc1).map((c) => c.key), ['nvidia:0:RTX 5090', 'amd:gfx1201', 'amd:gfx1036']);
// before any rate (first start): discrete by memory, integrated last; a removed card last of all; ties keep detection order
const fresh = [
card({ key: 'amd:gfx1036', kind: 'integrated', vram_mb: 512, hash_avg: 0, hash_now: 0, state: 'off' }),
card({ key: 'amd:gfx1201', kind: 'discrete', vram_mb: 16384, hash_avg: 0, hash_now: 0, state: 'waiting' }),
card({ key: 'nvidia:0:RTX 5090', hash_avg: 0, hash_now: 0, state: 'waiting' }),
card({ key: 'nvidia:1:RTX 5090', hash_avg: 0, hash_now: 0, state: 'waiting', removed_at: 5 }),
];
assert.deepEqual(V.shownCards(fresh).map((c) => c.key), ['nvidia:0:RTX 5090', 'amd:gfx1201', 'amd:gfx1036', 'nvidia:1:RTX 5090']);
// a small rate change does not reorder (5 MH/s buckets): 122 and 124 sort as equal and keep detection order
assert.deepEqual(V.shownCards([card({ key: 'a', hash_avg: 122 }), card({ key: 'b', hash_avg: 124 })]).map((c) => c.key), ['a', 'b']);
assert.equal(V.present(gone), false);
assert.equal(V.present(bad), false);
const t = V.toggle({ state: 'mining', paused: false, cards: [gone, bad] }, { synced: true }, {});