Skip to content

Commit

Permalink
refactor: update for beta & classic
Browse files Browse the repository at this point in the history
  • Loading branch information
airslice committed Aug 9, 2023
1 parent 241de7f commit da1062e
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 303 deletions.
96 changes: 55 additions & 41 deletions web/src/beta/lib/core/engines/Cesium/core/Imagery.test.ts
Original file line number Diff line number Diff line change
@@ -1,103 +1,117 @@
import { renderHook } from "@testing-library/react";
import { renderHook, act } from "@testing-library/react";
import { expect, test, vi } from "vitest";

import { type Tile, useImageryProviders } from "./Imagery";

test("useImageryProviders", () => {
test("useImageryProviders", async () => {
const provider = vi.fn(({ url }: { url?: string } = {}): any => ({ hoge: url }));
const provider2 = vi.fn(({ url }: { url?: string } = {}): any => ({ hoge2: url }));
const presets = { default: provider, foobar: provider2 };
const { result, rerender } = renderHook(
({ tiles, cesiumIonAccessToken }: { tiles: Tile[]; cesiumIonAccessToken?: string }) =>
useImageryProviders({
tiles,
presets,
cesiumIonAccessToken,
}),
{ initialProps: { tiles: [{ id: "1", tile_type: "default" }] } },
);
let result: any;
let rerender: any;

await act(async () => {
const renderResult = renderHook(
({ tiles, cesiumIonAccessToken }: { tiles: Tile[]; cesiumIonAccessToken?: string }) =>
useImageryProviders({
tiles,
presets,
cesiumIonAccessToken,
}),
{ initialProps: { tiles: [{ id: "1", tile_type: "default" }] } },
);
result = renderResult.result;
rerender = renderResult.rerender;
});

expect(result.current.providers).toEqual({ "1": ["default", undefined, { hoge: undefined }] });
expect(result.current.updated).toBe(true);
expect(provider).toBeCalledTimes(1);
const prevImageryProvider = result.current.providers["1"][2];

// re-render with same tiles
rerender({ tiles: [{ id: "1", tile_type: "default" }] });
await act(async () => {
rerender({ tiles: [{ id: "1", tile_type: "default" }] });
});

expect(result.current.providers).toEqual({ "1": ["default", undefined, { hoge: undefined }] });
expect(result.current.updated).toBe(false);
expect(result.current.providers["1"][2]).toBe(prevImageryProvider); // 1's provider should be reused
expect(provider).toBeCalledTimes(1);

// update a tile URL
rerender({ tiles: [{ id: "1", tile_type: "default", tile_url: "a" }] });
await act(async () => {
rerender({ tiles: [{ id: "1", tile_type: "default", tile_url: "a" }] });
});

expect(result.current.providers).toEqual({ "1": ["default", "a", { hoge: "a" }] });
expect(result.current.providers["1"][2]).not.toBe(prevImageryProvider);
expect(result.current.updated).toBe(true);
expect(provider).toBeCalledTimes(2);
expect(provider).toBeCalledWith({ url: "a" });
const prevImageryProvider2 = result.current.providers["1"][2];

// add a tile with URL
rerender({
tiles: [
{ id: "2", tile_type: "default" },
{ id: "1", tile_type: "default", tile_url: "a" },
],
await act(async () => {
rerender({
tiles: [
{ id: "2", tile_type: "default" },
{ id: "1", tile_type: "default", tile_url: "a" },
],
});
});

expect(result.current.providers).toEqual({
"2": ["default", undefined, { hoge: undefined }],
"1": ["default", "a", { hoge: "a" }],
});
expect(result.current.updated).toBe(true);
expect(result.current.providers["1"][2]).toBe(prevImageryProvider2); // 1's provider should be reused
expect(provider).toBeCalledTimes(3);
expect(provider).toBeCalledTimes(2);

// sort tiles
rerender({
tiles: [
{ id: "1", tile_type: "default", tile_url: "a" },
{ id: "2", tile_type: "default" },
],
await act(async () => {
rerender({
tiles: [
{ id: "1", tile_type: "default", tile_url: "a" },
{ id: "2", tile_type: "default" },
],
});
});

expect(result.current.providers).toEqual({
"1": ["default", "a", { hoge: "a" }],
"2": ["default", undefined, { hoge: undefined }],
});
expect(result.current.updated).toBe(true);
expect(result.current.providers["1"][2]).toBe(prevImageryProvider2); // 1's provider should be reused
expect(provider).toBeCalledTimes(3);
expect(provider).toBeCalledTimes(2);

// delete a tile
rerender({
tiles: [{ id: "1", tile_type: "default", tile_url: "a" }],
cesiumIonAccessToken: "a",
await act(async () => {
rerender({
tiles: [{ id: "1", tile_type: "default", tile_url: "a" }],
cesiumIonAccessToken: "a",
});
});

expect(result.current.providers).toEqual({
"1": ["default", "a", { hoge: "a" }],
});
expect(result.current.updated).toBe(true);
expect(result.current.providers["1"][2]).not.toBe(prevImageryProvider2);
expect(provider).toBeCalledTimes(4);
expect(provider).toBeCalledTimes(3);

// update a tile type
rerender({
tiles: [{ id: "1", tile_type: "foobar", tile_url: "u" }],
cesiumIonAccessToken: "a",
await act(async () => {
rerender({
tiles: [{ id: "1", tile_type: "foobar", tile_url: "u" }],
cesiumIonAccessToken: "a",
});
});

expect(result.current.providers).toEqual({
"1": ["foobar", "u", { hoge2: "u" }],
});
expect(result.current.updated).toBe(true);
expect(provider).toBeCalledTimes(4);
expect(provider).toBeCalledTimes(3);
expect(provider2).toBeCalledTimes(1);

rerender({ tiles: [] });
await act(async () => {
rerender({ tiles: [] });
});
expect(result.current.providers).toEqual({});
});
169 changes: 58 additions & 111 deletions web/src/beta/lib/core/engines/Cesium/core/Imagery.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ImageryProvider } from "cesium";
import { isEqual } from "lodash-es";
import { useCallback, useMemo, useRef, useLayoutEffect } from "react";
import { useMemo, useState, useEffect, useCallback, useRef } from "react";
import { ImageryLayer } from "resium";

import { tiles as tilePresets } from "./presets";
Expand Down Expand Up @@ -28,45 +27,44 @@ export type Props = {
};

export default function ImageryLayers({ tiles, cesiumIonAccessToken }: Props) {
const { providers, updated } = useImageryProviders({
const { providers } = useImageryProviders({
tiles,
cesiumIonAccessToken,
presets: tilePresets,
});

// force rerendering all layers when any provider is updated
// since Resium does not sort layers according to ImageryLayer component order
const counter = useRef(0);
useLayoutEffect(() => {
if (updated) counter.current++;
}, [providers, updated]);
const memoTiles = useMemo(
() =>
tiles
?.map(({ id, ...tile }) => ({ ...tile, id, provider: providers[id]?.[2] }))
.filter(({ provider }) => !!provider) ?? [],
[tiles, providers],
);

return (
<>
{tiles
?.map(({ id, ...tile }) => ({ ...tile, id, provider: providers[id]?.[2] }))
.map(({ id, tile_opacity: opacity, tile_minLevel: min, tile_maxLevel: max, provider }, i) =>
provider ? (
<ImageryLayer
key={`${id}_${i}_${counter.current}`}
imageryProvider={provider}
minimumTerrainLevel={min}
maximumTerrainLevel={max}
alpha={opacity}
index={i}
/>
) : null,
)}
{memoTiles.map(
({ id, tile_opacity: opacity, tile_minLevel: min, tile_maxLevel: max, provider }, i) => (
<ImageryLayer
key={`${id}_${i}`}
imageryProvider={provider}
minimumTerrainLevel={min}
maximumTerrainLevel={max}
alpha={opacity}
index={i}
/>
),
)}
</>
);
}

type Providers = {
[id: string]: [
string | undefined,
string | undefined,
Promise<ImageryProvider> | ImageryProvider,
];
[id: string]: [string | undefined, string | undefined, ImageryProvider];
};

type ResolvedProviders = {
[id: string]: ImageryProvider;
};

export function useImageryProviders({
Expand All @@ -82,92 +80,41 @@ export function useImageryProviders({
cesiumIonAccessToken?: string;
}) => Promise<ImageryProvider> | ImageryProvider | null;
};
}): { providers: Providers; updated: boolean } {
const newTile = useCallback(
(t: Tile, ciat?: string) =>
presets[t.tile_type || "default"]({ url: t.tile_url, cesiumIonAccessToken: ciat }),
[presets],
}): {
providers: Providers;
} {
const resolvedPresetProviders = useRef<ResolvedProviders>({});
const [providers, setProviders] = useState<Providers>({});

const providerKey = useCallback(
(t: Omit<Tile, "id">) => `${t.tile_type || "default"}_${t.tile_url}_${cesiumIonAccessToken}`,
[cesiumIonAccessToken],
);

const prevCesiumIonAccessToken = useRef(cesiumIonAccessToken);
const tileKeys = tiles.map(t => t.id).join(",");
const prevTileKeys = useRef(tileKeys);
const prevProviders = useRef<Providers>({});

// Manage TileProviders so that TileProvider does not need to be recreated each time tiles are updated.
const { providers, updated } = useMemo(() => {
const isCesiumAccessTokenUpdated = prevCesiumIonAccessToken.current !== cesiumIonAccessToken;
const prevProvidersKeys = Object.keys(prevProviders.current);
const added = tiles.map(t => t.id).filter(t => t && !prevProvidersKeys.includes(t));

const rawProviders = [
...Object.entries(prevProviders.current),
...added.map(a => [a, undefined] as const),
].map(([k, v]) => ({
key: k,
added: added.includes(k),
prevType: v?.[0],
prevUrl: v?.[1],
prevProvider: v?.[2],
tile: tiles.find(t => t.id === k),
}));

const providers = Object.fromEntries(
rawProviders
.map(
({
key,
added,
prevType,
prevUrl,
prevProvider,
tile,
}):
| [
string,
[
string | undefined,
string | undefined,
Promise<ImageryProvider> | ImageryProvider | null | undefined,
],
]
| null =>
!tile
? null
: [
key,
added ||
prevType !== tile.tile_type ||
prevUrl !== tile.tile_url ||
(isCesiumAccessTokenUpdated && (!tile.tile_type || tile.tile_type === "default"))
? [tile.tile_type, tile.tile_url, newTile(tile, cesiumIonAccessToken)]
: [prevType, prevUrl, prevProvider],
],
)
.filter(
(
e,
): e is [
string,
[string | undefined, string | undefined, Promise<ImageryProvider> | ImageryProvider],
] => !!e?.[1][2],
useEffect(() => {
Promise.all(
tiles.map(async t => {
if (!Object.keys(resolvedPresetProviders.current).includes(providerKey(t))) {
const newProvider = await presets[t.tile_type || "default"]({
url: t.tile_url,
cesiumIonAccessToken,
});
if (newProvider) {
resolvedPresetProviders.current[providerKey(t)] = newProvider;
}
}
}),
).then(() => {
setProviders(
Object.fromEntries(
tiles.map(({ id, ...t }) => [
id,
[t.tile_type, t.tile_url, resolvedPresetProviders.current[providerKey(t)]],
]),
),
);

const updated =
!!added.length ||
!!isCesiumAccessTokenUpdated ||
!isEqual(prevTileKeys.current, tileKeys) ||
rawProviders.some(
p => p.tile && (p.prevType !== p.tile.tile_type || p.prevUrl !== p.tile.tile_url),
);
});
}, [tiles, cesiumIonAccessToken, presets, resolvedPresetProviders, providerKey]);

prevTileKeys.current = tileKeys;
prevCesiumIonAccessToken.current = cesiumIonAccessToken;

return { providers, updated };
}, [cesiumIonAccessToken, tiles, tileKeys, newTile]);

prevProviders.current = providers;
return { providers, updated };
return { providers };
}
Loading

0 comments on commit da1062e

Please sign in to comment.