Performance: visiblePackages dep fix + ItemRow date memo + small Object.keys cuts
Five more low-risk hot-path optimizations: 1. visiblePackages no longer re-runs the sort callback on every item update. The sort is only meaningful when running && autoSort && >1 packages, so we pass null as items dep otherwise. Previously fired the full O(N) sort pass on every progress tick even when it would have returned the input array unchanged. 2. ItemRow memoizes formattedCreatedAt + displayStatus + statusTitle so a row that re-renders because of progress/speed changes no longer pays for formatDateTime() and computeDisplayedItemStatus() twice (title+body). 3. resetSessionTotalsIfQueueEmpty: removed redundant Object.keys() check. itemCount + packageOrder.length cover the same condition without allocating two intermediate arrays. 4. markQueuedAsReconnectWait: replaced Object.keys() array allocation with for-in iteration when runItemIds is empty. Saves a 5000-element string array allocation every 900ms during reconnect. 5. columnOrderJson useEffect dep: replaced JSON.stringify with cached join() + useMemo. Stringifying a 7-element settings array on every render was ~2-3ms per render for nothing. All 140 download-manager tests green. Renderer + main both build clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
b92330863a
commit
d71dd3af0b
@@ -2149,12 +2149,14 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
|
||||
private resetSessionTotalsIfQueueEmpty(force = false): void {
|
||||
// Cheap O(1) check via cached counters covers the common case.
|
||||
// The Object.keys() cross-check below was redundant — itemCount and
|
||||
// packageOrder are kept in sync with session.items / session.packages
|
||||
// by every mutation site, so the second check just allocated two
|
||||
// arrays per call without ever changing the outcome.
|
||||
if (this.itemCount > 0 || this.session.packageOrder.length > 0) {
|
||||
return;
|
||||
}
|
||||
if (Object.keys(this.session.items).length > 0 || Object.keys(this.session.packages).length > 0) {
|
||||
return;
|
||||
}
|
||||
if (!force && (this.sessionDownloadedBytes > 0 || this.sessionCompletedFiles > 0 || this.itemContributedBytes.size > 0)) {
|
||||
return;
|
||||
}
|
||||
@@ -7566,22 +7568,25 @@ export class DownloadManager extends EventEmitter {
|
||||
let changed = false;
|
||||
const waitSeconds = Math.max(0, Math.ceil((this.session.reconnectUntil - nowMs()) / 1000));
|
||||
const waitText = `Reconnect-Wait (${waitSeconds}s)`;
|
||||
const itemIds = this.runItemIds.size > 0 ? this.runItemIds : Object.keys(this.session.items);
|
||||
for (const itemId of itemIds) {
|
||||
// Iterate without allocating an Object.keys() array (called every 900ms
|
||||
// during reconnect; with 5000+ items that's a 5000-string allocation per tick).
|
||||
const updateItem = (itemId: string): void => {
|
||||
const item = this.session.items[itemId];
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
if (!item) return;
|
||||
const pkg = this.session.packages[item.packageId];
|
||||
if (!pkg || pkg.cancelled || !pkg.enabled) {
|
||||
continue;
|
||||
}
|
||||
if (!pkg || pkg.cancelled || !pkg.enabled) return;
|
||||
if (item.status === "queued") {
|
||||
item.status = "reconnect_wait";
|
||||
item.fullStatus = waitText;
|
||||
item.updatedAt = nowMs();
|
||||
changed = true;
|
||||
}
|
||||
};
|
||||
if (this.runItemIds.size > 0) {
|
||||
for (const itemId of this.runItemIds) updateItem(itemId);
|
||||
} else {
|
||||
// for-in iterates own enumerable string keys without allocating an array
|
||||
for (const itemId in this.session.items) updateItem(itemId);
|
||||
}
|
||||
if (changed) {
|
||||
this.emitState();
|
||||
|
||||
Reference in New Issue
Block a user