Skip to content

Commit

Permalink
fix: deleting feature process on reearth/core (#546)
Browse files Browse the repository at this point in the history
* fix: deleting feature process on reearth/core

* test
  • Loading branch information
keiya01 authored Mar 15, 2023
1 parent 6f5401e commit 39ecafc
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 67 deletions.
25 changes: 8 additions & 17 deletions src/core/Map/Layer/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useAtom } from "jotai";
import { isEqual, pick } from "lodash-es";
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react";

import { requestIdleCallbackWithRequiredWork } from "@reearth/util/idle";

import {
clearAllExpressionCaches,
computeAtom,
Expand Down Expand Up @@ -50,6 +52,10 @@ export default function useHooks({
(features: string[]) => set({ type: "deleteFeatures", features }),
[set],
);
const deleteComputedFeatures = useCallback(
(features: string[]) => set({ type: "deleteComputedFeatures", features }),
[set],
);
const forceUpdateFeatures = useCallback(() => set({ type: "forceUpdateFeatures" }), [set]);

useLayoutEffect(() => {
Expand Down Expand Up @@ -104,26 +110,10 @@ export default function useHooks({
prevForceUpdatableData.current = forceUpdatableData;
}, [layer, forceUpdateFeatures]);

// idleCallback is still experimental in ios
const ctx = typeof window !== "undefined" ? window : global;
const requestIdleCallbackShim = (
callback: (arg0: { didTimeout: boolean; timeRemaining: () => number }) => void,
) => {
const start = Date.now();
return ctx.setTimeout(function () {
callback({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 12 - (Date.now() - start));
},
});
});
};
const requestIdleCallback = ctx.requestIdleCallback || requestIdleCallbackShim;
// Clear expression cache if layer is unmounted
useEffect(
() => () => {
requestIdleCallback(() => {
requestIdleCallbackWithRequiredWork(() => {
// This is a little heavy task, and not critical for main functionality, so we can run this at idle time.
computedLayer?.originalFeatures.forEach(f => {
clearAllExpressionCaches(
Expand All @@ -144,6 +134,7 @@ export default function useHooks({
handleFeatureFetch: writeFeatures,
handleComputedFeatureFetch: writeComputedFeatures,
handleFeatureDelete: deleteFeatures,
handleComputedFeatureDelete: deleteComputedFeatures,
evalFeature,
};
}
Expand Down
3 changes: 3 additions & 0 deletions src/core/Map/Layer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type FeatureComponentProps = {
onFeatureFetch?: (features: Feature[]) => void;
onComputedFeatureFetch?: (feature: Feature[], computed: ComputedFeature[]) => void;
onFeatureDelete?: (features: string[]) => void;
onComputedFeatureDelete?: (features: string[]) => void;
evalFeature: EvalFeature;
} & CommonProps;

Expand All @@ -57,6 +58,7 @@ export default function LayerComponent({
const {
computedLayer,
handleFeatureDelete,
handleComputedFeatureDelete,
handleFeatureFetch,
handleComputedFeatureFetch,
handleFeatureRequest,
Expand All @@ -74,6 +76,7 @@ export default function LayerComponent({
<Feature
layer={computedLayer}
onFeatureDelete={handleFeatureDelete}
onComputedFeatureDelete={handleComputedFeatureDelete}
onFeatureFetch={handleFeatureFetch}
onComputedFeatureFetch={handleComputedFeatureFetch}
onFeatureRequest={handleFeatureRequest}
Expand Down
2 changes: 2 additions & 0 deletions src/core/Map/Layers/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ test("simple", () => {
onFeatureDelete: expect.any(Function),
onFeatureFetch: expect.any(Function),
onComputedFeatureFetch: expect.any(Function),
onComputedFeatureDelete: expect.any(Function),
onFeatureRequest: expect.any(Function),
evalFeature: expect.any(Function),
});
Expand All @@ -45,6 +46,7 @@ test("simple", () => {
onFeatureDelete: expect.any(Function),
onFeatureFetch: expect.any(Function),
onComputedFeatureFetch: expect.any(Function),
onComputedFeatureDelete: expect.any(Function),
onFeatureRequest: expect.any(Function),
evalFeature: expect.any(Function),
});
Expand Down
13 changes: 3 additions & 10 deletions src/core/engines/Cesium/Feature/Raster/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import {
WebMapServiceImageryProvider,
} from "cesium";
import { MVTImageryProvider } from "cesium-mvt-imagery-provider";
import md5 from "js-md5";
import { isEqual, pick } from "lodash-es";
import { useEffect, useMemo, useRef } from "react";
import { useCesium } from "resium";

import type { ComputedFeature, ComputedLayer, Feature, PolygonAppearance } from "../../..";
import { extractSimpleLayer, extractSimpleLayerData } from "../utils";
import { extractSimpleLayer, extractSimpleLayerData, generateIDWithMD5 } from "../utils";

import { Props } from "./types";

Expand Down Expand Up @@ -77,10 +76,7 @@ const idFromGeometry = (
":",
);

const hash = md5.create();
hash.update(id);

return hash.hex();
return generateIDWithMD5(id);
};

const makeFeatureFromPolygon = (
Expand Down Expand Up @@ -111,10 +107,7 @@ export const useMVT = ({
property,
layer,
evalFeature,
}: Pick<
Props,
"isVisible" | "property" | "layer" | "onComputedFeatureFetch" | "evalFeature" | "onFeatureDelete"
>) => {
}: Pick<Props, "isVisible" | "property" | "layer" | "evalFeature">) => {
const { show = true, minimumLevel, maximumLevel, credit } = property ?? {};
const { type, url, layers } = useData(layer);

Expand Down
11 changes: 2 additions & 9 deletions src/core/engines/Cesium/Feature/Raster/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,9 @@ import { extractSimpleLayer, extractSimpleLayerData, type FeatureComponentConfig
import { useMVT, useWMS } from "./hooks";
import type { Props } from "./types";

function Raster({
isVisible,
layer,
property,
onComputedFeatureFetch,
evalFeature,
onFeatureDelete,
}: Props) {
function Raster({ isVisible, layer, property, evalFeature }: Props) {
useWMS({ isVisible, layer, property });
useMVT({ isVisible, layer, property, evalFeature, onComputedFeatureFetch, onFeatureDelete });
useMVT({ isVisible, layer, property, evalFeature });

return null;
}
Expand Down
22 changes: 20 additions & 2 deletions src/core/engines/Cesium/Feature/Resource/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useRef } from "react";
import { KmlDataSource, CzmlDataSource, GeoJsonDataSource, useCesium } from "resium";

import { ComputedFeature, DataType, evalFeature, Feature } from "@reearth/core/mantle";
import { requestIdleCallbackWithRequiredWork } from "@reearth/util/idle";
import { getExtname } from "@reearth/util/path";

import type { ResourceAppearance } from "../../..";
Expand Down Expand Up @@ -36,7 +37,13 @@ type CachedFeature = {
raw: Entity;
};

export default function Resource({ isVisible, property, layer, onComputedFeatureFetch }: Props) {
export default function Resource({
isVisible,
property,
layer,
onComputedFeatureFetch,
onComputedFeatureDelete,
}: Props) {
const { show = true, clampToGround } = property ?? {};
const [type, url, updateClock] = useMemo((): [
ResourceAppearance["type"],
Expand All @@ -54,6 +61,7 @@ export default function Resource({ isVisible, property, layer, onComputedFeature
}, [property, layer]);
const { viewer } = useCesium() as { viewer?: Viewer };
const cachedFeatures = useRef<CachedFeature[]>([]);
const cachedFeatureIds = useRef(new Set<string>());

const ext = useMemo(() => (!type || type === "auto" ? getExtname(url) : undefined), [type, url]);
const actualType = ext ? types[ext] : type !== "auto" ? type : undefined;
Expand All @@ -70,9 +78,10 @@ export default function Resource({ isVisible, property, layer, onComputedFeature

attachTag(e, { layerId: layer?.id, featureId: feature?.id });

if (feature) {
if (feature && !cachedFeatureIds.current.has(feature.id)) {
features.push(feature);
cachedFeatures.current.push({ feature, raw: e });
cachedFeatureIds.current.add(feature.id);
}
if (computedFeature) {
computedFeatures.push(computedFeature);
Expand Down Expand Up @@ -123,6 +132,15 @@ export default function Resource({ isVisible, property, layer, onComputedFeature
});
}, [layer, viewer]);

useEffect(
() => () => {
requestIdleCallbackWithRequiredWork(() => {
onComputedFeatureDelete?.(Array.from(cachedFeatureIds.current.values()));
});
},
[], // eslint-disable-line react-hooks/exhaustive-deps
);

if (!isVisible || !show || !Component || !url) return null;

return (
Expand Down
41 changes: 34 additions & 7 deletions src/core/engines/Cesium/Feature/Tileset/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { CesiumComponentRef, useCesium } from "resium";

import { requestIdleCallbackWithRequiredWork } from "@reearth/util/idle";

import type { ComputedFeature, ComputedLayer, Feature, EvalFeature, SceneProperty } from "../../..";
import { layerIdField, sampleTerrainHeightFromCartesian } from "../../common";
import { lookupFeatures, translationWithClamping } from "../../utils";
Expand Down Expand Up @@ -78,17 +80,18 @@ const useFeature = ({
layer,
evalFeature,
onComputedFeatureFetch,
onFeatureDelete,
onComputedFeatureDelete,
}: {
id?: string;
tileset: MutableRefObject<Cesium3DTileset | undefined>;
layer?: ComputedLayer;
evalFeature: EvalFeature;
onComputedFeatureFetch?: (feature: Feature[], computed: ComputedFeature[]) => void;
onFeatureDelete?: (feature: string[]) => void;
onComputedFeatureDelete?: (feature: string[]) => void;
}) => {
const cachedFeaturesRef = useRef<CachedFeature[]>([]);
const cachedCalculatedLayerRef = useRef(layer);
const cachedFeatureIds = useRef(new Set<string>());
const layerId = layer?.id || id;

const attachComputedFeature = useCallback(
Expand Down Expand Up @@ -139,6 +142,7 @@ const useFeature = ({
raw: tileFeature,
};
cachedFeaturesRef.current.push(feature);
cachedFeatureIds.current.add(id);
return feature;
})();

Expand All @@ -161,15 +165,28 @@ const useFeature = ({
const coordinates = content.tile.boundingSphere.center;
const id = `${coordinates.x}-${coordinates.y}-${coordinates.z}-${tileFeature.featureId}`;
featureIds.push(id);

// Only delete cached id in here, removing cached feature lazily.
cachedFeatureIds.current.delete(id);
});
onFeatureDelete?.(featureIds);
onComputedFeatureDelete?.(featureIds);

if (
cachedFeaturesRef.current.length !== Array.from(cachedFeatureIds.current.values()).length
) {
queueMicrotask(() => {
cachedFeaturesRef.current = cachedFeaturesRef.current.filter(f =>
cachedFeatureIds.current.has(f.feature.id),
);
});
}
});
}, [
tileset,
cachedFeaturesRef,
attachComputedFeature,
onComputedFeatureFetch,
onFeatureDelete,
onComputedFeatureDelete,
layerId,
]);

Expand Down Expand Up @@ -238,6 +255,16 @@ const useFeature = ({
};
compute();
}, [computeFeatures]);

// Cleanup features
useEffect(
() => () => {
requestIdleCallbackWithRequiredWork(() => {
onComputedFeatureDelete?.(Array.from(cachedFeatureIds.current.values()));
});
},
[], // eslint-disable-line react-hooks/exhaustive-deps
);
};

export const useHooks = ({
Expand All @@ -250,7 +277,7 @@ export const useHooks = ({
meta,
evalFeature,
onComputedFeatureFetch,
onFeatureDelete,
onComputedFeatureDelete,
}: {
id: string;
boxId: string;
Expand All @@ -262,7 +289,7 @@ export const useHooks = ({
meta?: Record<string, unknown>;
evalFeature: EvalFeature;
onComputedFeatureFetch?: (feature: Feature[], computed: ComputedFeature[]) => void;
onFeatureDelete?: (feature: string[]) => void;
onComputedFeatureDelete?: (feature: string[]) => void;
}) => {
const { viewer } = useCesium();
const { tileset, styleUrl, edgeColor, edgeWidth, experimental_clipping } = property ?? {};
Expand Down Expand Up @@ -338,7 +365,7 @@ export const useHooks = ({
layer,
evalFeature,
onComputedFeatureFetch,
onFeatureDelete,
onComputedFeatureDelete,
});

const [terrainHeightEstimate, setTerrainHeightEstimate] = useState(0);
Expand Down
6 changes: 3 additions & 3 deletions src/core/engines/Cesium/Feature/Tileset/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function Tileset({
meta,
evalFeature,
onComputedFeatureFetch,
onFeatureDelete,
onComputedFeatureDelete,
...props
}: Props): JSX.Element | null {
const { shadows, colorBlendMode } = property ?? {};
Expand All @@ -37,7 +37,7 @@ function Tileset({
meta,
evalFeature,
onComputedFeatureFetch,
onFeatureDelete,
onComputedFeatureDelete,
});
const boxProperty = useMemo(
() => ({
Expand Down Expand Up @@ -70,7 +70,7 @@ function Tileset({
isVisible={builtinBoxProps.visible}
evalFeature={evalFeature}
onComputedFeatureFetch={onComputedFeatureFetch}
onFeatureDelete={onFeatureDelete}
onComputedFeatureDelete={onComputedFeatureDelete}
onLayerEdit={builtinBoxProps.handleLayerEdit}
/>
)}
Expand Down
17 changes: 14 additions & 3 deletions src/core/engines/Cesium/Feature/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import Polyline, { config as polylineConfig } from "./Polyline";
import Raster, { config as rasterConfig } from "./Raster";
import Resource, { config as resourceConfig } from "./Resource";
import Tileset, { config as tilesetConfig } from "./Tileset";
import { extractSimpleLayerData, FeatureComponent, FeatureComponentConfig } from "./utils";
import {
extractSimpleLayerData,
FeatureComponent,
FeatureComponentConfig,
generateIDWithMD5,
} from "./utils";

export * from "./utils";
export { context, type Context } from "./context";
Expand Down Expand Up @@ -84,14 +89,20 @@ export default function Feature({
<>
{displayType.map(k => {
const [C] = components[k] ?? [];
const isVisible = layer.layer.visible !== false && !isHidden;

// "noFeature" component should be recreated when the following value is changed.
// data.url, isVisible
const key = generateIDWithMD5(`${layer?.id || ""}_${k}_${data?.url}_${isVisible}`);

return (
<C
{...props}
key={`${layer?.id || ""}_${k}_${data?.url}`}
key={key}
id={`${layer.id}_${k}`}
property={pickProperty(k, layer) || layer[k]}
layer={layer}
isVisible={layer.layer.visible !== false && !isHidden}
isVisible={isVisible}
/>
);
})}
Expand Down
Loading

0 comments on commit 39ecafc

Please sign in to comment.