diff --git a/src/app/components/generator/PreviewPanel.tsx b/src/app/components/generator/PreviewPanel.tsx index 05f499d9..cd165319 100644 --- a/src/app/components/generator/PreviewPanel.tsx +++ b/src/app/components/generator/PreviewPanel.tsx @@ -9,7 +9,7 @@ import { BiomeSourcePreview, DecoratorPreview, DensityFunctionPreview, NoisePrev export const HasPreview = ['dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature'] type PreviewPanelProps = { - model: DataModel | null, + model: DataModel | undefined, version: VersionId, id: string, shown: boolean, diff --git a/src/app/components/generator/SourcePanel.tsx b/src/app/components/generator/SourcePanel.tsx index c6df44ac..0811de7c 100644 --- a/src/app/components/generator/SourcePanel.tsx +++ b/src/app/components/generator/SourcePanel.tsx @@ -52,8 +52,8 @@ interface Editor { type SourcePanelProps = { name: string, - model: DataModel | null, - blockStates: BlockStateRegistry | null, + model: DataModel | undefined, + blockStates: BlockStateRegistry | undefined, doCopy?: number, doDownload?: number, doImport?: number, diff --git a/src/app/components/generator/Tree.tsx b/src/app/components/generator/Tree.tsx index e813b53a..34c35416 100644 --- a/src/app/components/generator/Tree.tsx +++ b/src/app/components/generator/Tree.tsx @@ -7,8 +7,8 @@ import type { BlockStateRegistry, VersionId } from '../../services' type TreePanelProps = { version: VersionId, - model: DataModel | null, - blockStates: BlockStateRegistry | null, + model: DataModel | undefined, + blockStates: BlockStateRegistry | undefined, onError: (message: string) => unknown, } export function Tree({ version, model, blockStates, onError }: TreePanelProps) { diff --git a/src/app/components/previews/BiomeSourcePreview.tsx b/src/app/components/previews/BiomeSourcePreview.tsx index b2c433a8..be5b7e3d 100644 --- a/src/app/components/previews/BiomeSourcePreview.tsx +++ b/src/app/components/previews/BiomeSourcePreview.tsx @@ -58,14 +58,14 @@ export const BiomeSourcePreview = ({ model, data, shown, version }: PreviewProps onLeave() { setFocused(undefined) }, - }, [state, scale, configuredSeed, layers]) + }, [version, state, scale, configuredSeed, layers]) useEffect(() => { if (shown) { res.current = type === 'multi_noise' ? 4 : 1 redraw() } - }, [state, scale, configuredSeed, layers, shown]) + }, [version, state, scale, configuredSeed, layers, shown]) const changeScale = (newScale: number) => { offset.current[0] = offset.current[0] * scale / newScale diff --git a/src/app/components/previews/DecoratorPreview.tsx b/src/app/components/previews/DecoratorPreview.tsx index 9ab535a6..1da8ae35 100644 --- a/src/app/components/previews/DecoratorPreview.tsx +++ b/src/app/components/previews/DecoratorPreview.tsx @@ -11,6 +11,8 @@ export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => { const [scale, setScale] = useState(4) const [seed, setSeed] = useState(randomSeed()) + const state = JSON.stringify(data) + const { canvas, redraw } = useCanvas({ size() { return [scale * 16, scale * 16] @@ -18,14 +20,13 @@ export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => { async draw(img) { decorator(data, img, { seed, version, size: [scale * 16, 128, scale * 16] }) }, - }) + }, [version, state, seed]) - const state = JSON.stringify(data) useEffect(() => { if (shown) { redraw() } - }, [state, scale, seed, shown]) + }, [version, state, scale, seed, shown]) return <>
diff --git a/src/app/components/previews/DensityFunctionPreview.tsx b/src/app/components/previews/DensityFunctionPreview.tsx index 8b726ec9..3661d896 100644 --- a/src/app/components/previews/DensityFunctionPreview.tsx +++ b/src/app/components/previews/DensityFunctionPreview.tsx @@ -36,7 +36,7 @@ export const DensityFunctionPreview = ({ data, shown, version }: PreviewProps) = onLeave() { setFocused(undefined) }, - }, [state, seed]) + }, [version, state, seed]) useEffect(() => { if (scrollInterval.current) { @@ -51,7 +51,7 @@ export const DensityFunctionPreview = ({ data, shown, version }: PreviewProps) = }, 100) as any } } - }, [state, seed, shown, autoScroll]) + }, [version, state, seed, shown, autoScroll]) return <>
diff --git a/src/app/components/previews/NoisePreview.tsx b/src/app/components/previews/NoisePreview.tsx index 36e14799..fcc3a9dc 100644 --- a/src/app/components/previews/NoisePreview.tsx +++ b/src/app/components/previews/NoisePreview.tsx @@ -26,13 +26,13 @@ export const NoisePreview = ({ data, shown, version }: PreviewProps) => { offset.current[1] = offset.current[1] + dy * 256 redraw() }, - }, [state, scale, seed]) + }, [version, state, scale, seed]) useEffect(() => { if (shown) { redraw() } - }, [state, scale, seed, shown]) + }, [version, state, scale, seed, shown]) const changeScale = (newScale: number) => { offset.current[0] = offset.current[0] * scale / newScale diff --git a/src/app/components/previews/NoiseSettingsPreview.tsx b/src/app/components/previews/NoiseSettingsPreview.tsx index 8ac88a3d..b61a6fb5 100644 --- a/src/app/components/previews/NoiseSettingsPreview.tsx +++ b/src/app/components/previews/NoiseSettingsPreview.tsx @@ -62,7 +62,7 @@ export const NoiseSettingsPreview = ({ data, shown, version }: PreviewProps) => } })() } - }, [state, seed, shown, biome, biomeScale, biomeDepth, autoScroll]) + }, [version, state, seed, shown, biome, biomeScale, biomeDepth, autoScroll]) const allBiomes = useMemo(() => CachedCollections?.get('worldgen/biome') ?? [], [version]) diff --git a/src/app/hooks/useAsync.ts b/src/app/hooks/useAsync.ts index e0499022..5cce9276 100644 --- a/src/app/hooks/useAsync.ts +++ b/src/app/hooks/useAsync.ts @@ -1,13 +1,14 @@ import type { Inputs } from 'preact/hooks' import { useEffect } from 'preact/hooks' -import type { AsyncState } from './useAsyncFn' +import type { AsyncCancel, AsyncState } from './useAsyncFn' import { useAsyncFn } from './useAsyncFn' -export function useAsync( - fn: () => Promise, +export function useAsync( + fn: () => Promise, inputs: Inputs = [], -): AsyncState { - const [state, callback] = useAsyncFn Promise>(fn, inputs, { loading: true }) + initialState: AsyncState = { loading: true }, +): AsyncState { + const [state, callback] = useAsyncFn Promise>(fn, inputs, initialState) useEffect(() => { callback() diff --git a/src/app/hooks/useAsyncFn.ts b/src/app/hooks/useAsyncFn.ts index 5fe877e5..e736c87b 100644 --- a/src/app/hooks/useAsyncFn.ts +++ b/src/app/hooks/useAsyncFn.ts @@ -20,11 +20,13 @@ export type AsyncState = { value: T, } -export function useAsyncFn Promise>( +export const AsyncCancel = Symbol('async-cancel') + +export function useAsyncFn Promise>( fn: T, inputs: Inputs = [], initialState: AsyncState = { loading: false }, -): [AsyncState, (...args: Parameters) => Promise] { +): [AsyncState, (...args: Parameters) => Promise] { const [state, setState] = useState>(initialState) const isMounted = useRef(false) const lastCallId = useRef(0) @@ -34,7 +36,7 @@ export function useAsyncFn Promise>( return () => isMounted.current = false }, []) - const callback = useCallback((...args: Parameters): Promise => { + const callback = useCallback((...args: Parameters): Promise => { const callId = ++lastCallId.current if (!state.loading) { setState(prev => ({ ...prev, loading: true })) @@ -42,7 +44,7 @@ export function useAsyncFn Promise>( return fn(...args).then( value => { - if (isMounted.current && callId === lastCallId.current) { + if (isMounted.current && callId === lastCallId.current && value !== AsyncCancel) { setState({ value, loading: false }) } return value diff --git a/src/app/hooks/useSearchParam.ts b/src/app/hooks/useSearchParam.ts index fe7961c0..5bfedbd5 100644 --- a/src/app/hooks/useSearchParam.ts +++ b/src/app/hooks/useSearchParam.ts @@ -25,6 +25,7 @@ export function useSearchParam(param: string): [string | undefined, (value: stri const changeValue = useCallback((newValue: string | undefined, replace?: boolean) => { if (newValue !== value) { + setValue(newValue) const params = new URLSearchParams(location.search) if (newValue === undefined || newValue.length === 0) { params.delete(param) diff --git a/src/app/pages/Generator.tsx b/src/app/pages/Generator.tsx index 9873af74..eeac48bc 100644 --- a/src/app/pages/Generator.tsx +++ b/src/app/pages/Generator.tsx @@ -5,9 +5,9 @@ import config from '../../config.json' import { Analytics } from '../Analytics' import { Ad, Btn, BtnMenu, ErrorPanel, HasPreview, Octicon, PreviewPanel, SearchList, SourcePanel, TextInput, Tree } from '../components' import { useLocale, useProject, useTitle, useVersion } from '../contexts' -import { useActiveTimeout, useModel, useSearchParam } from '../hooks' +import { AsyncCancel, useActiveTimeout, useAsync, useModel, useSearchParam } from '../hooks' import { getOutput } from '../schema/transformOutput' -import type { BlockStateRegistry, VersionId } from '../services' +import type { VersionId } from '../services' import { checkVersion, fetchPreset, getBlockStates, getCollections, getModel, getSnippet, shareSnippet, SHARE_KEY } from '../services' import { Store } from '../Store' import { cleanUrl, deepEqual, getGenerator } from '../Utils' @@ -50,35 +50,6 @@ export function Generator({}: Props) { const [currentPreset, setCurrentPreset] = useSearchParam('preset') const [sharedSnippetId, setSharedSnippetId] = useSearchParam(SHARE_KEY) const ignoreChange = useRef(false) - useEffect(() => { - if (model && currentPreset) { - loadPreset(currentPreset).then(preset => { - ignoreChange.current = true - model.reset(DataModel.wrapLists(preset), false) - setSharedSnippetId(undefined) - }) - } else if (model && sharedSnippetId) { - getSnippet(sharedSnippetId).then(s => loadSnippet(model, s)) - } - }, [currentPreset, sharedSnippetId]) - - const loadSnippet = (model: DataModel, snippet: any) => { - if (snippet.version && snippet.version !== version) { - changeVersion(snippet.version, false) - } - if (snippet.type && snippet.type !== gen.id) { - const snippetGen = config.generators.find(g => g.id === snippet.type) - if (snippetGen) { - route(`${cleanUrl(snippetGen.url)}?${SHARE_KEY}=${snippet.id}`) - } - } - if (snippet.show_preview && !previewShown) { - setPreviewShown(true) - setSourceShown(false) - } - model.reset(DataModel.wrapLists(snippet.data), false) - } - const backup = useMemo(() => Store.getBackup(gen.id), [gen.id]) const loadBackup = () => { @@ -87,27 +58,51 @@ export function Generator({}: Props) { } } - const [model, setModel] = useState(null) - const [blockStates, setBlockStates] = useState(null) - useEffect(() => { - setError(null) - setModel(null) - getBlockStates(version) - .then(b => setBlockStates(b)) - getModel(version, gen.id) - .then(async m => { - Analytics.setGenerator(gen.id) - if (currentPreset) { - const preset = await loadPreset(currentPreset) - m.reset(DataModel.wrapLists(preset), false) - } else if (sharedSnippetId) { - const snippet = await getSnippet(sharedSnippetId) - loadSnippet(m, snippet) + const { value } = useAsync(async () => { + let data: unknown = undefined + if (currentPreset && sharedSnippetId) { + setSharedSnippetId(undefined) + return AsyncCancel + } + if (currentPreset) { + data = await loadPreset(currentPreset) + } else if (sharedSnippetId) { + const snippet = await getSnippet(sharedSnippetId) + let cancel = false + if (snippet.version && snippet.version !== version) { + changeVersion(snippet.version, false) + cancel = true + } + if (snippet.type && snippet.type !== gen.id) { + const snippetGen = config.generators.find(g => g.id === snippet.type) + if (snippetGen) { + route(`${cleanUrl(snippetGen.url)}?${SHARE_KEY}=${snippet.id}`) + cancel = true } - setModel(m) - }) - .catch(e => { console.error(e); setError(e) }) - }, [version, gen.id]) + } + if (cancel) { + return AsyncCancel + } + if (snippet.show_preview && !previewShown) { + setPreviewShown(true) + setSourceShown(false) + } + data = snippet.data + } + const [model, blockStates] = await Promise.all([ + getModel(version, gen.id), + getBlockStates(version), + ]) + if (data) { + ignoreChange.current = true + model.reset(DataModel.wrapLists(data), false) + } + Analytics.setGenerator(gen.id) + return { model, blockStates } + }, [gen.id, version, sharedSnippetId, currentPreset]) + + const model = value?.model + const blockStates = value?.blockStates const [dirty, setDirty] = useState(false) useModel(model, () => { diff --git a/src/app/previews/Deepslate.ts b/src/app/previews/Deepslate.ts index 58e7393b..ddb29ee9 100644 --- a/src/app/previews/Deepslate.ts +++ b/src/app/previews/Deepslate.ts @@ -60,6 +60,7 @@ export class Deepslate { } public generateChunks(minX: number, width: number, biome = 'unknown') { + minX = Math.floor(minX) if (!this.settingsCache) { throw new Error('Tried to generate chunks before settings are loaded') } @@ -108,6 +109,8 @@ export class Deepslate { } public getBlockState(x: number, y: number) { + x = Math.floor(x) + y = Math.floor(y) const chunk = this.chunksCache.find(c => this.d.ChunkPos.minBlockX(c.pos) <= x && this.d.ChunkPos.maxBlockX(c.pos) >= x) return chunk?.getBlockState(this.d.BlockPos.create(x, y, Deepslate.Z)) }