Skip to content

Commit

Permalink
feat: support interval fetching data on reearth/core (#449)
Browse files Browse the repository at this point in the history
* feat: support interval fetching data

* refactor: use window
  • Loading branch information
keiya01 committed Feb 10, 2023
1 parent 3174b17 commit 4061743
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 7 deletions.
20 changes: 19 additions & 1 deletion src/core/Map/Layer/hooks.ts
@@ -1,5 +1,5 @@
import { useAtom } from "jotai";
import { useCallback, useLayoutEffect, useMemo } from "react";
import { useCallback, useLayoutEffect, useMemo, useRef } from "react";

import { computeAtom, DataType, type Atom, evalFeature, ComputedFeature } from "../../mantle";
import type { DataRange, Feature, Layer } from "../../mantle";
Expand Down Expand Up @@ -34,6 +34,7 @@ export default function useHooks(
(features: string[]) => set({ type: "deleteFeatures", features }),
[set],
);
const forceUpdateFeatures = useCallback(() => set({ type: "forceUpdateFeatures" }), [set]);

useLayoutEffect(() => {
set({ type: "updateDelegatedDataTypes", delegatedDataTypes: delegatedDataTypes ?? [] });
Expand All @@ -56,6 +57,23 @@ export default function useHooks(
});
}, [layer, set]);

const intervalId = useRef<number>();
useLayoutEffect(() => {
const data = layer?.type === "simple" ? layer.data : undefined;

if (!data?.updateInterval || !data?.url) {
return;
}

intervalId.current = window.setInterval(forceUpdateFeatures, data.updateInterval);

return () => {
if (intervalId.current) {
clearInterval(intervalId.current);
}
};
}, [layer, forceUpdateFeatures]);

return {
computedLayer,
handleFeatureRequest: requestFetch,
Expand Down
101 changes: 101 additions & 0 deletions src/core/mantle/atoms/compute.test.ts
Expand Up @@ -667,6 +667,107 @@ test("computeAtom only value", async () => {
expect(fetchDataMock).toBeCalledTimes(3);
});

test("forceUpdateFeatures", async () => {
const fetchDataMock = vi.spyOn(DataCache, "fetchData");
fetchDataMock.mockImplementation(async data => {
return [
{
type: "feature",
id: (data as any).test_id || "",
geometry: data.value?.geometry || { type: "Point", coordinates: [0, 0] },
},
];
});
const { result } = renderHook(() => {
const atoms = useMemo(() => computeAtom(doubleKeyCacheAtom<string, string, Feature[]>()), []);
const [result, set] = useAtom(atoms);
return { result, set };
});

expect(result.current.result).toBeUndefined();

// Set data.url
await act(async () => {
await waitFor(() =>
result.current.set({
type: "setLayer",
layer: {
id: "xxx",
type: "simple",
data,
},
}),
);
});

expect(result.current.result).toEqual({
id: "xxx",
layer: {
id: "xxx",
type: "simple",
data,
},
status: "ready",
features: toComputedFeature(features),
originalFeatures: [...features],
});

expect(fetchDataMock).toBeCalledTimes(1);

// Set same data
await act(async () => {
await waitFor(() =>
result.current.set({
type: "setLayer",
layer: {
id: "xxx",
type: "simple",
data,
},
}),
);
});

expect(result.current.result).toEqual({
id: "xxx",
layer: {
id: "xxx",
type: "simple",
data,
},
status: "ready",
features: toComputedFeature(features),
originalFeatures: [...features],
});

// Should cache is used
expect(fetchDataMock).toBeCalledTimes(1);

// Force update
await act(async () => {
await waitFor(() =>
result.current.set({
type: "forceUpdateFeatures",
}),
);
});

expect(result.current.result).toEqual({
id: "xxx",
layer: {
id: "xxx",
type: "simple",
data,
},
status: "ready",
features: toComputedFeature(features),
originalFeatures: [...features],
});

// Should fetch is invoked
expect(fetchDataMock).toBeCalledTimes(2);
});

vi.mock("../evaluator", (): { evalLayer: typeof evalLayer } => ({
evalLayer: async (layer: LayerSimple, ctx: EvalContext) => {
if (!layer.data) return { layer: {}, features: undefined };
Expand Down
19 changes: 15 additions & 4 deletions src/core/mantle/atoms/compute.ts
Expand Up @@ -26,7 +26,8 @@ export type Command =
| { type: "writeComputedFeatures"; value: { feature: Feature[]; computed: ComputedFeature[] } }
| { type: "deleteFeatures"; features: string[] }
| { type: "override"; overrides?: Record<string, any> }
| { type: "updateDelegatedDataTypes"; delegatedDataTypes: DataType[] };
| { type: "updateDelegatedDataTypes"; delegatedDataTypes: DataType[] }
| { type: "forceUpdateFeatures" };

export function computeAtom(cache?: typeof globalDataFeaturesCache) {
const delegatedDataTypes = atom<DataType[]>([]);
Expand Down Expand Up @@ -67,7 +68,7 @@ export function computeAtom(cache?: typeof globalDataFeaturesCache) {
};
});

const compute = atom(null, async (get, set, _: any) => {
const compute = atom(null, async (get, set, value: { forceUpdateData: boolean } | undefined) => {
const currentLayer = get(layer);
if (!currentLayer) return;

Expand Down Expand Up @@ -123,18 +124,19 @@ export function computeAtom(cache?: typeof globalDataFeaturesCache) {

// Ignore cache if data is embedded
if (
!value?.forceUpdateData &&
allFeatures &&
// Check if data has cache key
DATA_CACHE_KEYS.every(k => !!data[k])
) {
return flattenAllFeatures;
}

if (!shouldFetch(flattenAllFeatures)) {
if (!value?.forceUpdateData && !shouldFetch(flattenAllFeatures)) {
return flattenAllFeatures;
}

await set(dataAtoms.fetch, { data, layerId });
await set(dataAtoms.fetch, { data, layerId, forceUpdateData: value?.forceUpdateData });
return getterAll(layerId, data)?.flat() ?? [];
};

Expand Down Expand Up @@ -239,6 +241,12 @@ export function computeAtom(cache?: typeof globalDataFeaturesCache) {
await set(compute, undefined);
});

const forceUpdateFeatures = atom(null, async (get, set, _: any) => {
const currentLayer = get(layer);
if (currentLayer?.type !== "simple" || !currentLayer?.data) return;
await set(compute, { forceUpdateData: true });
});

return atom<ComputedLayer | undefined, Command>(
g => g(get),
async (_, s, value) => {
Expand All @@ -264,6 +272,9 @@ export function computeAtom(cache?: typeof globalDataFeaturesCache) {
case "updateDelegatedDataTypes":
await s(updateDelegatedDataTypes, value.delegatedDataTypes);
break;
case "forceUpdateFeatures":
await s(forceUpdateFeatures, undefined);
break;
}
},
);
Expand Down
11 changes: 9 additions & 2 deletions src/core/mantle/atoms/data.ts
Expand Up @@ -60,12 +60,19 @@ export function dataAtom(cacheAtoms = globalDataFeaturesCache) {

const fetch = atom(
null,
async (get, set, value: { data: Data; range?: DataRange; layerId: string }) => {
async (
get,
set,
value: { data: Data; range?: DataRange; layerId: string; forceUpdateData?: boolean },
) => {
const k = dataKey(value.layerId, value.data);
if (!k) return;

const rk = rangeKey(value.range);
const cached = !value.data.value && value.data.url ? get(cacheAtoms.get)(k, rk) : undefined;
const cached =
!value.forceUpdateData && !value.data.value && value.data.url
? get(cacheAtoms.get)(k, rk)
: undefined;
if (cached || get(fetching).findIndex(e => e[0] === k && e[1] === rk) >= 0) return;

try {
Expand Down
1 change: 1 addition & 0 deletions src/core/mantle/types/index.ts
Expand Up @@ -63,6 +63,7 @@ export type Data = {
value?: any;
layers?: string | string[];
jsonProperties?: string[];
updateInterval?: number; // milliseconds
time?: {
property?: string;
interval?: number; // milliseconds
Expand Down

0 comments on commit 4061743

Please sign in to comment.