refactor(team): extract local state epoch registry
This commit is contained in:
parent
4a561f2cd2
commit
7d08a10d6f
3 changed files with 91 additions and 15 deletions
|
|
@ -38,6 +38,13 @@ import {
|
||||||
normalizeTeamGetDataOptions,
|
normalizeTeamGetDataOptions,
|
||||||
} from '../team/teamDataRequestKeys';
|
} from '../team/teamDataRequestKeys';
|
||||||
import { selectTeamDataForName } from '../team/teamDataSelectors';
|
import { selectTeamDataForName } from '../team/teamDataSelectors';
|
||||||
|
import {
|
||||||
|
captureTeamLocalStateEpoch,
|
||||||
|
clearAllTeamLocalStateEpochs,
|
||||||
|
hasTeamLocalStateEpoch,
|
||||||
|
invalidateTeamLocalStateEpoch,
|
||||||
|
isTeamLocalStateEpochCurrent,
|
||||||
|
} from '../team/teamLocalStateEpoch';
|
||||||
import {
|
import {
|
||||||
areInboxMessageArraysEquivalent,
|
areInboxMessageArraysEquivalent,
|
||||||
clearTeamMessageSelectorCaches,
|
clearTeamMessageSelectorCaches,
|
||||||
|
|
@ -158,7 +165,6 @@ const inFlightTeamMemberActivityMetaRequests = new Map<string, Promise<void>>();
|
||||||
const pendingFreshTeamMemberActivityMetaRefreshes = new Set<string>();
|
const pendingFreshTeamMemberActivityMetaRefreshes = new Set<string>();
|
||||||
const pendingTeamPendingReplyRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
const pendingTeamPendingReplyRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||||
const lastResolvedTeamDataRefreshAtByTeam = new Map<string, number>();
|
const lastResolvedTeamDataRefreshAtByTeam = new Map<string, number>();
|
||||||
const teamLocalStateEpochByTeam = new Map<string, number>();
|
|
||||||
let inFlightGlobalTasksRefresh: Promise<void> | null = null;
|
let inFlightGlobalTasksRefresh: Promise<void> | null = null;
|
||||||
let pendingFreshGlobalTasksRefresh = false;
|
let pendingFreshGlobalTasksRefresh = false;
|
||||||
const memberSpawnStatusesIpcBackoffUntilByTeam = new Map<string, number>();
|
const memberSpawnStatusesIpcBackoffUntilByTeam = new Map<string, number>();
|
||||||
|
|
@ -232,7 +238,7 @@ export function __resetTeamSliceModuleStateForTests(): void {
|
||||||
pendingTeamPendingReplyRefreshTimers.clear();
|
pendingTeamPendingReplyRefreshTimers.clear();
|
||||||
clearAllPendingReplyRefreshWaits();
|
clearAllPendingReplyRefreshWaits();
|
||||||
lastResolvedTeamDataRefreshAtByTeam.clear();
|
lastResolvedTeamDataRefreshAtByTeam.clear();
|
||||||
teamLocalStateEpochByTeam.clear();
|
clearAllTeamLocalStateEpochs();
|
||||||
memberSpawnStatusesIpcBackoffUntilByTeam.clear();
|
memberSpawnStatusesIpcBackoffUntilByTeam.clear();
|
||||||
teamRefreshBurstDiagnostics.clear();
|
teamRefreshBurstDiagnostics.clear();
|
||||||
memberSpawnUiEqualLastWarnAtByTeam.clear();
|
memberSpawnUiEqualLastWarnAtByTeam.clear();
|
||||||
|
|
@ -432,18 +438,6 @@ function buildTeamScopedProgressTombstones(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function captureTeamLocalStateEpoch(teamName: string): number {
|
|
||||||
return teamLocalStateEpochByTeam.get(teamName) ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTeamLocalStateEpochCurrent(teamName: string, epoch: number): boolean {
|
|
||||||
return captureTeamLocalStateEpoch(teamName) === epoch;
|
|
||||||
}
|
|
||||||
|
|
||||||
function invalidateTeamLocalStateEpoch(teamName: string): void {
|
|
||||||
teamLocalStateEpochByTeam.set(teamName, captureTeamLocalStateEpoch(teamName) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function beginInFlightTeamDataRefresh(teamName: string): symbol {
|
function beginInFlightTeamDataRefresh(teamName: string): symbol {
|
||||||
const token = Symbol(teamName);
|
const token = Symbol(teamName);
|
||||||
const existing = inFlightRefreshTeamDataCalls.get(teamName);
|
const existing = inFlightRefreshTeamDataCalls.get(teamName);
|
||||||
|
|
@ -663,7 +657,7 @@ export function __getTeamScopedTransientStateForTests(teamName: string): {
|
||||||
hasPendingFreshMemberActivityMetaRefresh:
|
hasPendingFreshMemberActivityMetaRefresh:
|
||||||
pendingFreshTeamMemberActivityMetaRefreshes.has(teamName),
|
pendingFreshTeamMemberActivityMetaRefreshes.has(teamName),
|
||||||
hasLastResolvedTeamDataRefresh: lastResolvedTeamDataRefreshAtByTeam.has(teamName),
|
hasLastResolvedTeamDataRefresh: lastResolvedTeamDataRefreshAtByTeam.has(teamName),
|
||||||
hasCurrentLocalStateEpoch: teamLocalStateEpochByTeam.has(teamName),
|
hasCurrentLocalStateEpoch: hasTeamLocalStateEpoch(teamName),
|
||||||
hasMemberSpawnStatusesIpcBackoff: memberSpawnStatusesIpcBackoffUntilByTeam.has(teamName),
|
hasMemberSpawnStatusesIpcBackoff: memberSpawnStatusesIpcBackoffUntilByTeam.has(teamName),
|
||||||
hasTeamRefreshBurstDiagnostics: teamRefreshBurstDiagnostics.has(teamName),
|
hasTeamRefreshBurstDiagnostics: teamRefreshBurstDiagnostics.has(teamName),
|
||||||
hasMemberSpawnUiEqualLastWarn: memberSpawnUiEqualLastWarnAtByTeam.has(teamName),
|
hasMemberSpawnUiEqualLastWarn: memberSpawnUiEqualLastWarnAtByTeam.has(teamName),
|
||||||
|
|
|
||||||
25
src/renderer/store/team/teamLocalStateEpoch.ts
Normal file
25
src/renderer/store/team/teamLocalStateEpoch.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
const teamLocalStateEpochByTeam = new Map<string, number>();
|
||||||
|
|
||||||
|
export function captureTeamLocalStateEpoch(teamName: string): number {
|
||||||
|
return teamLocalStateEpochByTeam.get(teamName) ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTeamLocalStateEpochCurrent(teamName: string, epoch: number): boolean {
|
||||||
|
return captureTeamLocalStateEpoch(teamName) === epoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function invalidateTeamLocalStateEpoch(teamName: string): void {
|
||||||
|
teamLocalStateEpochByTeam.set(teamName, captureTeamLocalStateEpoch(teamName) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasTeamLocalStateEpoch(teamName: string): boolean {
|
||||||
|
return teamLocalStateEpochByTeam.has(teamName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearTeamLocalStateEpoch(teamName: string): void {
|
||||||
|
teamLocalStateEpochByTeam.delete(teamName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAllTeamLocalStateEpochs(): void {
|
||||||
|
teamLocalStateEpochByTeam.clear();
|
||||||
|
}
|
||||||
57
test/renderer/store/teamLocalStateEpoch.test.ts
Normal file
57
test/renderer/store/teamLocalStateEpoch.test.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
captureTeamLocalStateEpoch,
|
||||||
|
clearAllTeamLocalStateEpochs,
|
||||||
|
clearTeamLocalStateEpoch,
|
||||||
|
hasTeamLocalStateEpoch,
|
||||||
|
invalidateTeamLocalStateEpoch,
|
||||||
|
isTeamLocalStateEpochCurrent,
|
||||||
|
} from '../../../src/renderer/store/team/teamLocalStateEpoch';
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
clearAllTeamLocalStateEpochs();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('teamLocalStateEpoch', () => {
|
||||||
|
it('starts missing teams at epoch zero without materializing an entry', () => {
|
||||||
|
expect(captureTeamLocalStateEpoch('my-team')).toBe(0);
|
||||||
|
expect(isTeamLocalStateEpochCurrent('my-team', 0)).toBe(true);
|
||||||
|
expect(hasTeamLocalStateEpoch('my-team')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments epochs independently per team', () => {
|
||||||
|
invalidateTeamLocalStateEpoch('my-team');
|
||||||
|
invalidateTeamLocalStateEpoch('my-team');
|
||||||
|
invalidateTeamLocalStateEpoch('other-team');
|
||||||
|
|
||||||
|
expect(captureTeamLocalStateEpoch('my-team')).toBe(2);
|
||||||
|
expect(captureTeamLocalStateEpoch('other-team')).toBe(1);
|
||||||
|
expect(isTeamLocalStateEpochCurrent('my-team', 1)).toBe(false);
|
||||||
|
expect(isTeamLocalStateEpochCurrent('my-team', 2)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears one team epoch without touching other teams', () => {
|
||||||
|
invalidateTeamLocalStateEpoch('my-team');
|
||||||
|
invalidateTeamLocalStateEpoch('other-team');
|
||||||
|
|
||||||
|
clearTeamLocalStateEpoch('my-team');
|
||||||
|
|
||||||
|
expect(captureTeamLocalStateEpoch('my-team')).toBe(0);
|
||||||
|
expect(hasTeamLocalStateEpoch('my-team')).toBe(false);
|
||||||
|
expect(captureTeamLocalStateEpoch('other-team')).toBe(1);
|
||||||
|
expect(hasTeamLocalStateEpoch('other-team')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears all materialized epochs', () => {
|
||||||
|
invalidateTeamLocalStateEpoch('my-team');
|
||||||
|
invalidateTeamLocalStateEpoch('other-team');
|
||||||
|
|
||||||
|
clearAllTeamLocalStateEpochs();
|
||||||
|
|
||||||
|
expect(hasTeamLocalStateEpoch('my-team')).toBe(false);
|
||||||
|
expect(hasTeamLocalStateEpoch('other-team')).toBe(false);
|
||||||
|
expect(captureTeamLocalStateEpoch('my-team')).toBe(0);
|
||||||
|
expect(captureTeamLocalStateEpoch('other-team')).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue