Refactor: create deepslate abstraction

This commit is contained in:
Misode
2022-05-08 03:18:07 +02:00
parent 1871b977fa
commit 4e51d41c54
5 changed files with 175 additions and 142 deletions

23
package-lock.json generated
View File

@@ -23,6 +23,7 @@
"comment-json": "^4.1.1",
"deepslate": "^0.11.0-beta.1",
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
"deepslate-rs": "^0.1.6",
"highlight.js": "^11.5.1",
"howler": "^2.2.3",
@@ -1788,6 +1789,18 @@
"pako": "^2.0.3"
}
},
"node_modules/deepslate-1.18.2": {
"name": "deepslate",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.9.0.tgz",
"integrity": "sha512-juHNoXVu7+Gejw0xk4AwIt99FgIe/nkxTqJTv3bTlcfXINcF3VNlg1l6MnSiVTUFnuFHZl81woE9lVMAMp53Aw==",
"license": "MIT",
"dependencies": {
"gl-matrix": "^3.3.0",
"md5": "^2.3.0",
"pako": "^2.0.3"
}
},
"node_modules/deepslate-rs": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/deepslate-rs/-/deepslate-rs-0.1.6.tgz",
@@ -6441,6 +6454,16 @@
"pako": "^2.0.3"
}
},
"deepslate-1.18.2": {
"version": "npm:deepslate@0.9.0",
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.9.0.tgz",
"integrity": "sha512-juHNoXVu7+Gejw0xk4AwIt99FgIe/nkxTqJTv3bTlcfXINcF3VNlg1l6MnSiVTUFnuFHZl81woE9lVMAMp53Aw==",
"requires": {
"gl-matrix": "^3.3.0",
"md5": "^2.3.0",
"pako": "^2.0.3"
}
},
"deepslate-rs": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/deepslate-rs/-/deepslate-rs-0.1.6.tgz",

View File

@@ -29,6 +29,7 @@
"comment-json": "^4.1.1",
"deepslate": "^0.11.0-beta.1",
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
"deepslate-rs": "^0.1.6",
"highlight.js": "^11.5.1",
"howler": "^2.2.3",

View File

@@ -36,8 +36,9 @@ export function useCanvas({ size, draw, onDrag, onHover, onLeave }: {
if (!(dx === 0 && dy === 0)) {
dragPending.current = [dragPending.current[0] + dx, dragPending.current[1] + dy]
if (!dragBusy.current) {
if (!dragRequest.current) return
cancelAnimationFrame(dragRequest.current)
if (dragRequest.current) {
cancelAnimationFrame(dragRequest.current)
}
dragRequest.current = requestAnimationFrame(async () => {
if (!canvas.current) return
dragBusy.current = true

View File

@@ -0,0 +1,129 @@
import { DataModel } from '@mcschema/core'
import * as deepslate19 from 'deepslate/worldgen'
import type { VersionId } from '../services'
import { checkVersion, fetchAllPresets } from '../services'
import { deepClone, deepEqual } from '../Utils'
export class Deepslate {
private d = deepslate19
private loadedVersion: VersionId | undefined
private readonly registriesLoaded = new Set<VersionId>()
private static readonly Z = 0
private cacheState: unknown
private settingsCache: NoiseSettings | undefined
private generatorCache: ChunkGenerator | undefined
private chunksCache: Chunk[] = []
public async loadVersion(version: VersionId) {
console.debug('Load deepslate version', this.loadedVersion, '->', version)
if (checkVersion(version, '1.19')) {
this.d = deepslate19
} else if (checkVersion(version, '1.18.2')) {
this.d = await import('deepslate-1.18.2') as any
} else {
this.d = await import('deepslate-1.18') as any
}
if (this.d.WorldgenRegistries) {
if (!this.registriesLoaded.has(version)) {
const REGISTRIES: [string, keyof typeof this.d.WorldgenRegistries, { fromJson(obj: unknown): any}][] = [
['worldgen/noise', 'NOISE', this.d.NoiseParameters],
['worldgen/density_function', 'DENSITY_FUNCTION', this.d.DensityFunction],
]
await Promise.all(REGISTRIES.map(async ([id, name, parser]) => {
const entries = await fetchAllPresets(version, id)
const registry = new this.d.Registry<typeof parser>(this.d.Identifier.create(id))
for (const [key, value] of entries.entries()) {
registry.register(this.d.Identifier.parse(key), parser.fromJson(value))
}
this.d.WorldgenRegistries[name].assign(registry as any)
}))
this.registriesLoaded.add(version)
}
}
console.debug('Finished loading deepslate version', this.loadedVersion, '->', version)
this.loadedVersion = version
}
public loadChunkGenerator(settings: unknown, seed: bigint, biome = 'unknown') {
const newCacheState = [settings, `${seed}`, biome]
if (!deepEqual(this.cacheState, newCacheState)) {
const biomeSource = new this.d.FixedBiome(this.d.Identifier.parse(biome))
const noiseSettings = this.d.NoiseGeneratorSettings.fromJson(DataModel.unwrapLists(settings))
const chunkGenerator = new this.d.NoiseChunkGenerator(seed, biomeSource, noiseSettings)
this.settingsCache = noiseSettings.noise
this.generatorCache = chunkGenerator
this.chunksCache = []
this.cacheState = deepClone(newCacheState)
}
}
public generateChunks(minX: number, width: number, biome = 'unknown') {
if (!this.settingsCache) {
throw new Error('Tried to generate chunks before settings are loaded')
}
const minY = this.settingsCache.minY
const height = this.settingsCache.height
return [...Array(Math.ceil(width / 16) + 1)].map((_, i) => {
const x = (minX >> 4) + i
const cached = this.chunksCache.find(c => c.pos[0] === x)
if (cached) {
return cached
}
const chunk = new this.d.Chunk(minY, height, this.d.ChunkPos.create(x, Deepslate.Z >> 4))
if (!this.generatorCache) {
throw new Error('Tried to generate chunks before generator is loaded')
}
this.generatorCache.fill(chunk, true)
this.generatorCache.buildSurface(chunk, biome)
this.chunksCache.push(chunk)
return chunk
})
}
public loadDensityFunction(state: unknown, seed: bigint) {
const random = this.d.XoroshiroRandom.create(seed).forkPositional()
const settings = this.d.NoiseSettings.fromJson({
min_y: -64,
height: 384,
size_horizontal: 1,
size_vertical: 2,
sampling: { xz_scale: 1, y_scale: 1, xz_factor: 80, y_factor: 160 },
bottom_slide: { target: 0.1171875, size: 3, offset: 0 },
top_slide: { target: -0.078125, size: 2, offset: 8 },
terrain_shaper: { offset: 0.044, factor: 4, jaggedness: 0 },
})
this.settingsCache = settings
const originalFn = this.d.DensityFunction.fromJson(state)
return originalFn.mapAll(new this.d.NoiseRouter.Visitor(random, settings))
}
public getNoiseSettings(): NoiseSettings {
if (!this.settingsCache) {
throw new Error('Tried to access noise settings when they are not loaded')
}
return this.settingsCache
}
public getBlockState(x: number, y: number) {
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))
}
}
interface NoiseSettings {
minY: number,
height: number,
}
interface ChunkGenerator {
fill(chunk: Chunk, onlyFirstZ?: boolean): void
buildSurface(chunk: Chunk, biome: string): void
}
interface Chunk {
readonly pos: deepslate19.ChunkPos;
getBlockState(pos: deepslate19.BlockPos): deepslate19.BlockState;
}

View File

@@ -1,9 +1,7 @@
import { DataModel } from '@mcschema/core'
import type { BlockState } from 'deepslate/worldgen'
import { BlockPos, Chunk, ChunkPos, clampedMap, DensityFunction, FixedBiome, Identifier, NoiseChunkGenerator, NoiseGeneratorSettings, NoiseParameters, NoiseRouter, NoiseSettings, Registry, WorldgenRegistries, XoroshiroRandom } from 'deepslate/worldgen'
import { BlockState, clampedMap, DensityFunction } from 'deepslate/worldgen'
import type { VersionId } from '../services'
import { checkVersion, fetchAllPresets } from '../services'
import { deepClone, deepEqual } from '../Utils'
import { checkVersion } from '../services'
import { Deepslate } from './Deepslate'
import { NoiseChunkGenerator as OldNoiseChunkGenerator } from './noise/NoiseChunkGenerator'
export type NoiseSettingsOptions = {
@@ -16,8 +14,6 @@ export type NoiseSettingsOptions = {
version: VersionId,
}
const Z = 0
const colors: Record<string, [number, number, number]> = {
'minecraft:air': [150, 160, 170],
'minecraft:water': [20, 80, 170],
@@ -37,27 +33,21 @@ const colors: Record<string, [number, number, number]> = {
'minecraft:end_stone': [200, 200, 140],
}
let cacheState: any
let generatorCache: NoiseChunkGenerator
let chunkCache: Chunk[] = []
const registryCache = new Map<VersionId, Registry<Registry<any>>>()
const DEEPSLATE = new Deepslate()
export async function noiseSettings(state: any, img: ImageData, options: NoiseSettingsOptions) {
if (checkVersion(options.version, '1.18')) {
if (checkVersion(options.version, '1.18.2')) {
await initRegistries(options.version)
}
const { settings, generator } = await getCached(state, options)
const slice = new LevelSlice(-options.offset, options.width, settings.noise.minY, settings.noise.height)
slice.generate(generator, options.biome)
await DEEPSLATE.loadVersion(options.version)
DEEPSLATE.loadChunkGenerator(state, options.seed, options.biome)
DEEPSLATE.generateChunks(-options.offset, options.width, options.biome)
const noise = DEEPSLATE.getNoiseSettings()
const data = img.data
for (let x = 0; x < options.width; x += 1) {
for (let y = 0; y < settings.noise.height; y += 1) {
const i = x * 4 + (settings.noise.height-y-1) * 4 * img.width
const state = slice.getBlockState([x - options.offset, y + settings.noise.minY, Z])
for (let y = 0; y < noise.height; y += 1) {
const i = x * 4 + (noise.height-y-1) * 4 * img.width
const state = DEEPSLATE.getBlockState(x - options.offset, y + noise.minY) ?? BlockState.AIR
const color = colors[state.getName().toString()] ?? [0, 0, 0]
data[i] = color[0]
data[i + 1] = color[1]
@@ -86,22 +76,20 @@ export async function noiseSettings(state: any, img: ImageData, options: NoiseSe
}
export function getNoiseBlock(x: number, y: number) {
const chunk = chunkCache.find(c => ChunkPos.minBlockX(c.pos) <= x && ChunkPos.maxBlockX(c.pos) >= x)
if (!chunk) {
return undefined
}
return chunk.getBlockState(BlockPos.create(x, y, Z))
return DEEPSLATE.getBlockState(x, y)
}
export async function densityFunction(state: any, img: ImageData, options: NoiseSettingsOptions) {
const { fn, settings } = await createDensityFunction(state, options)
await DEEPSLATE.loadVersion(options.version)
const fn = DEEPSLATE.loadDensityFunction(state, options.seed)
const noise = DEEPSLATE.getNoiseSettings()
const arr = Array(options.width * settings.height)
const arr = Array(options.width * noise.height)
let min = Infinity
let max = -Infinity
for (let x = 0; x < options.width; x += 1) {
for (let y = 0; y < settings.height; y += 1) {
const i = x + (settings.height-y-1) * options.width
for (let y = 0; y < noise.height; y += 1) {
const i = x + (noise.height-y-1) * options.width
const density = fn.compute(DensityFunction.context(x - options.offset, y, 0))
min = Math.min(min, density)
max = Math.max(max, density)
@@ -110,7 +98,7 @@ export async function densityFunction(state: any, img: ImageData, options: Noise
}
const data = img.data
for (let i = 0; i < options.width * settings.height; i += 1) {
for (let i = 0; i < options.width * noise.height; i += 1) {
const color = Math.floor(clampedMap(arr[i], min, max, 0, 256))
data[4 * i] = color
data[4 * i + 1] = color
@@ -119,75 +107,6 @@ export async function densityFunction(state: any, img: ImageData, options: Noise
}
}
async function createDensityFunction(state: any, options: NoiseSettingsOptions) {
await initRegistries(options.version)
const random = XoroshiroRandom.create(options.seed).forkPositional()
const settings = NoiseSettings.fromJson({
min_y: -64,
height: 384,
size_horizontal: 1,
size_vertical: 2,
sampling: { xz_scale: 1, y_scale: 1, xz_factor: 80, y_factor: 160 },
bottom_slide: { target: 0.1171875, size: 3, offset: 0 },
top_slide: { target: -0.078125, size: 2, offset: 8 },
terrain_shaper: { offset: 0.044, factor: 4, jaggedness: 0 },
})
const originalFn = DensityFunction.fromJson(state)
const fn = originalFn.mapAll(new NoiseRouter.Visitor(random, settings))
return {
fn,
settings,
}
}
const Registries: [string, { fromJson(obj: unknown): any}][] = [
['worldgen/noise', NoiseParameters],
['worldgen/density_function', DensityFunction],
]
async function initRegistries(version: VersionId) {
const rootRegistries = registryCache.get(version) ?? new Registry(new Identifier('misode', 'temp'))
if (!registryCache.has(version)) {
await Promise.all(Registries.map(([id, c]) => fetchRegistry(version, rootRegistries, id, c)))
registryCache.set(version, rootRegistries)
}
WorldgenRegistries.DENSITY_FUNCTION.clear().assign(rootRegistries.getOrThrow(Identifier.create('worldgen/density_function')))
WorldgenRegistries.NOISE.clear().assign(rootRegistries.getOrThrow(Identifier.create('worldgen/noise')))
}
async function fetchRegistry<T extends { fromJson(obj: unknown): T }>(version: VersionId, root: Registry<Registry<unknown>>, id: string, clazz: T) {
const entries = await fetchAllPresets(version, id)
const registry = new Registry<typeof clazz>(Identifier.create(id))
for (const [key, value] of entries.entries()) {
registry.register(Identifier.parse(key), clazz.fromJson(value))
}
root.register(registry.key, registry)
}
async function getCached(state: unknown, options: NoiseSettingsOptions) {
const settings = NoiseGeneratorSettings.fromJson(DataModel.unwrapLists(state))
const newState = [state, `${options.seed}`, options.biome]
if (!deepEqual(newState, cacheState)) {
cacheState = deepClone(newState)
chunkCache = []
if (checkVersion(options.version, '1.18.2')) {
const biomeSource = new FixedBiome(Identifier.create('unknown'))
generatorCache = new NoiseChunkGenerator(options.seed, biomeSource, settings)
} else {
const deepslate18 = await import('deepslate-1.18')
const biomeSource = new deepslate18.FixedBiome('unknown')
generatorCache = new deepslate18.NoiseChunkGenerator(options.seed, biomeSource, settings as any) as any
}
}
return {
settings,
generator: generatorCache,
}
}
function getColor(noise: number[], y: number): number {
if (noise[y] > 0) {
return 0
@@ -197,43 +116,3 @@ function getColor(noise: number[], y: number): number {
}
return 255
}
class LevelSlice {
private readonly chunks: Chunk[]
private readonly done: boolean[]
constructor(
private readonly minX: number,
width: number,
minY: number,
height: number,
) {
this.done = []
this.chunks = [...Array(Math.ceil(width / 16) + 1)]
.map((_, i) => {
const x = (minX >> 4) + i
const cached = chunkCache.find(c => c.pos[0] === x)
if (cached) {
this.done[i] = true
return cached
}
return new Chunk(minY, height, ChunkPos.create(x, Z >> 4))
})
}
public generate(generator: NoiseChunkGenerator, forcedBiome?: string) {
this.chunks.forEach((chunk, i) => {
if (!this.done[i]) {
generator.fill(chunk, true)
generator.buildSurface(chunk, forcedBiome)
this.done[i] = true
chunkCache.push(chunk)
}
})
}
public getBlockState(pos: BlockPos): BlockState {
const chunkIndex = (pos[0] >> 4) - (this.minX >> 4)
return this.chunks[chunkIndex].getBlockState(pos)
}
}