perf(app): pause cached tab background work

This commit is contained in:
t8y2 2026-06-02 23:41:11 +08:00
parent abb214c620
commit b0856e51bc
4 changed files with 132 additions and 29 deletions

View File

@ -1032,7 +1032,7 @@ onUnmounted(() => {
@set-default-database="setActiveDatabaseAsDefault"
@clear-default-database="clearActiveDefaultDatabase"
/>
<KeepAlive :max="20">
<KeepAlive :max="8">
<ContentArea
ref="contentAreaRef"
:key="activeTab.id"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch, shallowRef, computed } from "vue";
import { ref, onMounted, onBeforeUnmount, onActivated, onDeactivated, watch, shallowRef, computed } from "vue";
import { Play, Copy, TextSelect } from "lucide-vue-next";
import { useI18n } from "vue-i18n";
import type { CompletionContext } from "@codemirror/autocomplete";
@ -192,6 +192,8 @@ let setSqlDiagnosticsEffect: import("@codemirror/state").StateEffectType<SqlSema
let semanticDiagnostics: SqlSemanticDiagnostic[] = [];
let semanticDiagnosticTimer: ReturnType<typeof setTimeout> | null = null;
let semanticDiagnosticRunId = 0;
let editorIsActive = true;
let tableReferenceDropListenerRegistered = false;
function editorThemeAppearance() {
return isDark.value ? "dark" : "light";
@ -724,6 +726,7 @@ async function refreshSemanticDiagnostics() {
}
function scheduleSemanticDiagnostics(delay = 500) {
if (!editorIsActive) return;
if (semanticDiagnosticTimer) clearTimeout(semanticDiagnosticTimer);
semanticDiagnosticTimer = setTimeout(() => {
semanticDiagnosticTimer = null;
@ -809,6 +812,18 @@ function onTableReferenceDropEvent(event: Event) {
}
}
function registerTableReferenceDropListener() {
if (tableReferenceDropListenerRegistered) return;
window.addEventListener(DBX_TABLE_REFERENCE_DROP_EVENT, onTableReferenceDropEvent);
tableReferenceDropListenerRegistered = true;
}
function unregisterTableReferenceDropListener() {
if (!tableReferenceDropListenerRegistered) return;
window.removeEventListener(DBX_TABLE_REFERENCE_DROP_EVENT, onTableReferenceDropEvent);
tableReferenceDropListenerRegistered = false;
}
let completionEpoch = 0;
let completionDebounceTimer: ReturnType<typeof setTimeout> | null = null;
@ -1528,7 +1543,7 @@ onMounted(async () => {
view.value = new EditorView({ state, parent: editorRef.value });
syncContextMenuState(view.value);
syncEditorFontCssVars(liveFontSize.value, ss.fontFamily);
window.addEventListener(DBX_TABLE_REFERENCE_DROP_EVENT, onTableReferenceDropEvent);
registerTableReferenceDropListener();
cachedTables = [];
scheduleSemanticDiagnostics();
@ -1625,10 +1640,28 @@ watch(
{ deep: true },
);
onBeforeUnmount(() => {
zoomCommitScheduler.dispose();
function pauseQueryEditorBackgroundWork() {
editorIsActive = false;
semanticDiagnosticRunId++;
if (semanticDiagnosticTimer) clearTimeout(semanticDiagnosticTimer);
window.removeEventListener(DBX_TABLE_REFERENCE_DROP_EVENT, onTableReferenceDropEvent);
semanticDiagnosticTimer = null;
completionEpoch++;
unregisterTableReferenceDropListener();
}
function resumeQueryEditorBackgroundWork() {
editorIsActive = true;
registerTableReferenceDropListener();
scheduleSemanticDiagnostics();
}
onActivated(resumeQueryEditorBackgroundWork);
onDeactivated(pauseQueryEditorBackgroundWork);
onBeforeUnmount(() => {
pauseQueryEditorBackgroundWork();
zoomCommitScheduler.dispose();
view.value?.destroy();
});

View File

@ -4,7 +4,17 @@ const globalDdlOpen = ref(false);
</script>
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, useSlots, watch, type Component } from "vue";
import {
computed,
nextTick,
onMounted,
onUnmounted,
onActivated,
onDeactivated,
useSlots,
watch,
type Component,
} from "vue";
import { useI18n } from "vue-i18n";
import {
ArrowUp,
@ -2982,6 +2992,7 @@ const canvasRenderStyleKey = computed(
);
let canvasResizeObserver: ResizeObserver | null = null;
let canvasDrawFrame = 0;
let dataGridIsActive = true;
function toggleDataGridRenderMode() {
settingsStore.updateEditorSettings({
@ -3000,6 +3011,7 @@ function canvasScrollerElement(): HTMLElement | null {
}
function syncCanvasViewport() {
if (!dataGridIsActive) return;
const scroller = canvasScrollerElement();
if (!scroller) return;
canvasViewportWidth.value = scroller.clientWidth;
@ -3013,6 +3025,7 @@ function syncCanvasViewport() {
function attachCanvasResizeObserver() {
canvasResizeObserver?.disconnect();
canvasResizeObserver = null;
if (!dataGridIsActive) return;
if (!useCanvasGridRows.value) return;
const scroller = canvasScrollerElement();
if (!scroller) return;
@ -3023,6 +3036,7 @@ function attachCanvasResizeObserver() {
}
function scheduleCanvasDraw() {
if (!dataGridIsActive) return;
if (!useCanvasGridRows.value || canvasDrawFrame) return;
canvasDrawFrame = requestAnimationFrame(() => {
canvasDrawFrame = 0;
@ -3345,11 +3359,26 @@ watch(
],
scheduleCanvasDraw,
);
onMounted(() => nextTick(attachCanvasResizeObserver));
onUnmounted(() => {
function pauseCanvasGridWork() {
dataGridIsActive = false;
canvasResizeObserver?.disconnect();
if (canvasDrawFrame) cancelAnimationFrame(canvasDrawFrame);
});
canvasResizeObserver = null;
if (canvasDrawFrame) {
cancelAnimationFrame(canvasDrawFrame);
canvasDrawFrame = 0;
}
}
function resumeCanvasGridWork() {
dataGridIsActive = true;
nextTick(attachCanvasResizeObserver);
}
onMounted(resumeCanvasGridWork);
onActivated(resumeCanvasGridWork);
onDeactivated(pauseCanvasGridWork);
onUnmounted(pauseCanvasGridWork);
function setRowStatusFilter(value: string) {
rowStatusFilter.value = value as RowStatusFilter;
@ -4044,10 +4073,7 @@ function getTransposeRecordWidth(recordIndex: number): number {
function ensureTransposeRecordWidths(count: number) {
if (transposeRecordWidths.value.length !== count) {
const prev = transposeRecordWidths.value;
const next = new Array(count);
for (let i = 0; i < count; i++) {
next[i] = i < prev.length ? prev[i] : calcTransposeRecordWidth(i);
}
const next = Array.from({ length: count }, (_, i) => (i < prev.length ? prev[i] : calcTransposeRecordWidth(i)));
transposeRecordWidths.value = next;
}
}
@ -4749,21 +4775,32 @@ const loadingElapsed = ref(0);
let _loadingTimer: ReturnType<typeof setInterval> | undefined;
let _loadingStart = 0;
function stopLoadingElapsedTimer() {
clearInterval(_loadingTimer);
_loadingTimer = undefined;
}
function startLoadingElapsedTimer() {
stopLoadingElapsedTimer();
if (!dataGridIsActive || !props.loading) return;
_loadingStart = Date.now();
loadingElapsed.value = 0;
_loadingTimer = setInterval(() => {
loadingElapsed.value = Date.now() - _loadingStart;
}, 100);
}
watch(
() => props.loading,
(isLoading) => {
clearInterval(_loadingTimer);
stopLoadingElapsedTimer();
console.info(isLoading ? "[DBX][DataGrid:loading:start]" : "[DBX][DataGrid:loading:stop]", {
traceId: dataGridTraceId,
cacheKey: props.cacheKey,
elapsedSinceSetup: dataGridElapsed(),
});
if (isLoading) {
_loadingStart = Date.now();
loadingElapsed.value = 0;
_loadingTimer = setInterval(() => {
loadingElapsed.value = Date.now() - _loadingStart;
}, 100);
startLoadingElapsedTimer();
} else {
nextTick(() => {
requestAnimationFrame(() => {
@ -4778,6 +4815,9 @@ watch(
},
);
onActivated(startLoadingElapsedTimer);
onDeactivated(stopLoadingElapsedTimer);
onUnmounted(() => {
cleanupFrames();
onSearchSplitResizeEnd();

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, nextTick, ref, onMounted, onUnmounted, watch } from "vue";
import { computed, nextTick, ref, onMounted, onUnmounted, onActivated, onDeactivated, watch } from "vue";
import { useI18n } from "vue-i18n";
import {
Search,
@ -95,6 +95,8 @@ const createKeyField = ref("");
const createKeyScore = ref("0");
const createKeyError = ref("");
let searchRequestId = 0;
let redisBrowserIsActive = true;
let redisDbFlushedListenerRegistered = false;
const valueQuery = computed(() => searchPattern.value.trim());
const effectivePattern = computed(() => (searchMode.value === "key" ? searchPattern.value.trim() || "*" : "*"));
@ -216,6 +218,7 @@ async function fillInitialKeyBatch(requestId: number) {
}
async function loadKeys() {
if (!redisBrowserIsActive) return;
const requestId = ++searchRequestId;
loading.value = true;
flatKeys.value = [];
@ -574,23 +577,50 @@ function onSearchKeydown(event: KeyboardEvent) {
void loadKeys();
}
onUnmounted(() => {
searchRequestId++;
if (searchTimer) clearTimeout(searchTimer);
window.removeEventListener("dbx-redis-db-flushed", onRedisDbFlushed);
});
function onRedisDbFlushed(event: Event) {
const detail = (event as CustomEvent<{ connectionId: string; db: number }>).detail;
if (!detail || detail.connectionId !== props.connectionId || detail.db !== props.db) return;
resetLoadedKeys();
}
onMounted(() => {
function registerRedisDbFlushedListener() {
if (redisDbFlushedListenerRegistered) return;
window.addEventListener("dbx-redis-db-flushed", onRedisDbFlushed);
redisDbFlushedListenerRegistered = true;
}
function unregisterRedisDbFlushedListener() {
if (!redisDbFlushedListenerRegistered) return;
window.removeEventListener("dbx-redis-db-flushed", onRedisDbFlushed);
redisDbFlushedListenerRegistered = false;
}
function pauseRedisBrowserBackgroundWork() {
redisBrowserIsActive = false;
searchRequestId++;
loading.value = false;
loadingMore.value = false;
if (searchTimer) clearTimeout(searchTimer);
searchTimer = null;
unregisterRedisDbFlushedListener();
}
function resumeRedisBrowserBackgroundWork() {
redisBrowserIsActive = true;
registerRedisDbFlushedListener();
}
onMounted(() => {
resumeRedisBrowserBackgroundWork();
void loadKeys();
});
onActivated(resumeRedisBrowserBackgroundWork);
onDeactivated(pauseRedisBrowserBackgroundWork);
onUnmounted(pauseRedisBrowserBackgroundWork);
watch(
() => props.db,
(db) => {