feat(oceanbase-oracle): split execution time from client wait

This commit is contained in:
efeire authored and GitHub committed 2026-09-20 12:31:47 +08:00
1 parent 1294708eea
commit 33afb0b432
58 files changed
+843 -11

No files matched your search

@@ -0,0 +1,157 @@
// @vitest-environment happy-dom
import { createApp, defineComponent, h, nextTick, onMounted, onUpdated, ref } from "vue";
import { afterEach, describe, expect, it, vi } from "vitest";
import { useResultViewUpdateTiming } from "../useResultViewUpdateTiming";
const mounted: Array<ReturnType<typeof createApp>> = [];
afterEach(() => {
mounted.splice(0).forEach((app) => app.unmount());
document.body.innerHTML = "";
vi.restoreAllMocks();
});
describe("useResultViewUpdateTiming", () => {
it("measures through the Vue DOM update and replaces the value for a new result", async () => {
let clock = 100;
vi.spyOn(performance, "now").mockImplementation(() => clock);
const result = ref({ id: 1 });
const host = document.createElement("div");
document.body.append(host);
const app = createApp(
defineComponent({
setup() {
const { elapsedMs } = useResultViewUpdateTiming(
() => result.value,
() => true,
() => "dom",
);
onMounted(() => {
clock = 119;
});
onUpdated(() => {
if (result.value.id === 2 && clock === 130) clock = 147;
});
return () => h("div", [h("span", String(result.value.id)), h("output", elapsedMs.value ?? "pending")]);
},
}),
);
mounted.push(app);
app.mount(host);
await nextTick();
await nextTick();
expect(host.querySelector("output")?.textContent).toBe("19");
clock = 130;
result.value = { id: 2 };
await nextTick();
await nextTick();
await nextTick();
expect(host.querySelector("span")?.textContent).toBe("2");
expect(host.querySelector("output")?.textContent).toBe("17");
});
it("waits for a successful Canvas draw and rejects draws for stale results", async () => {
let clock = 10;
vi.spyOn(performance, "now").mockImplementation(() => clock);
const result = ref({ id: 1 });
const mode = ref<"dom" | "canvas">("canvas");
let canvasDrawCompleted!: (drawnResult: { id: number }) => void;
const host = document.createElement("div");
document.body.append(host);
const app = createApp(
defineComponent({
setup() {
const timing = useResultViewUpdateTiming(
() => result.value,
() => true,
() => mode.value,
);
canvasDrawCompleted = timing.canvasDrawCompleted;
return () => h("output", timing.elapsedMs.value ?? "pending");
},
}),
);
mounted.push(app);
app.mount(host);
await nextTick();
await nextTick();
expect(host.textContent).toBe("pending");
const staleResult = result.value;
result.value = { id: 2 };
canvasDrawCompleted(staleResult);
clock = 20;
await nextTick();
await nextTick();
expect(host.textContent).toBe("pending");
clock = 35;
canvasDrawCompleted(result.value);
await nextTick();
expect(host.textContent).toBe("15");
mode.value = "dom";
await nextTick();
await nextTick();
await nextTick();
expect(host.textContent).toBe("0");
});
it("suppresses measurements while disabled", async () => {
const result = ref({ id: 1 });
const enabled = ref(false);
const host = document.createElement("div");
document.body.append(host);
const app = createApp(
defineComponent({
setup() {
const timing = useResultViewUpdateTiming(
() => result.value,
() => enabled.value,
() => "canvas",
);
return () => h("output", timing.elapsedMs.value ?? "pending");
},
}),
);
mounted.push(app);
app.mount(host);
enabled.value = true;
await nextTick();
expect(host.textContent).toBe("pending");
enabled.value = false;
await nextTick();
expect(host.textContent).toBe("pending");
});
it("does not finish a stale DOM tick after switching to Canvas or after unmount", async () => {
const result = ref({ id: 1 });
const mode = ref<"dom" | "canvas">("dom");
let timing!: ReturnType<typeof useResultViewUpdateTiming<{ id: number }>>;
const host = document.createElement("div");
document.body.append(host);
const app = createApp(
defineComponent({
setup() {
timing = useResultViewUpdateTiming(
() => result.value,
() => true,
() => mode.value,
);
return () => h("output", timing.elapsedMs.value ?? "pending");
},
}),
);
mounted.push(app);
app.mount(host);
mode.value = "canvas";
await nextTick();
await nextTick();
expect(host.textContent).toBe("pending");
app.unmount();
timing.canvasDrawCompleted(result.value);
expect(timing.elapsedMs.value).toBeUndefined();
});
});
@@ -0,0 +1,59 @@
import { computed, nextTick, onActivated, onDeactivated, onUnmounted, ref, shallowRef, watch } from "vue";
type ViewMode = "dom" | "canvas";
/** Times the grid's DOM update or synchronous Canvas draw, excluding screen paint. */
export function useResultViewUpdateTiming<T extends object>(result: () => T, enabled: () => boolean, mode: () => ViewMode) {
const lastMeasurement = shallowRef<{ result: T; mode: ViewMode; elapsedMs: number } | null>(null);
const active = ref(true);
let generation = 0;
let pending: { result: T; mode: ViewMode; startedAt: number; generation: number } | null = null;
function finish(current: T, currentMode: ViewMode, expectedGeneration: number) {
if (!active.value || !enabled() || result() !== current || mode() !== currentMode) return;
if (!pending || pending.result !== current || pending.mode !== currentMode || pending.generation !== expectedGeneration) return;
lastMeasurement.value = { result: current, mode: currentMode, elapsedMs: Math.max(0, Math.round(performance.now() - pending.startedAt)) };
pending = null;
}
function start(current: T, currentMode: ViewMode, isEnabled: boolean) {
const currentGeneration = ++generation;
lastMeasurement.value = null;
pending = active.value && isEnabled ? { result: current, mode: currentMode, startedAt: performance.now(), generation: currentGeneration } : null;
if (pending && currentMode === "dom") void nextTick(() => finish(current, "dom", currentGeneration));
}
watch(
() => [result(), mode(), enabled()] as const,
([current, currentMode, isEnabled]) => start(current, currentMode, isEnabled),
{ immediate: true },
);
onDeactivated(() => {
active.value = false;
generation++;
pending = null;
lastMeasurement.value = null;
});
onActivated(() => {
if (!active.value) {
active.value = true;
start(result(), mode(), enabled());
}
});
onUnmounted(() => {
active.value = false;
generation++;
pending = null;
});
return {
elapsedMs: computed(() => {
const measured = lastMeasurement.value;
return active.value && enabled() && measured?.result === result() && measured.mode === mode() ? measured.elapsedMs : undefined;
}),
canvasDrawCompleted: (drawnResult: T) => {
if (pending?.mode === "canvas") finish(drawnResult, "canvas", pending.generation);
},
};
}