From 24fd4d4fc0ab57661b7cd49306d3d4395eb6acea Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 19 Aug 2024 14:01:13 +0300 Subject: [PATCH] feat: implement fast world loading with file descriptor & http backend! (#182) --- README.MD | 26 +++++++++++++++++++++++++- server.js | 1 + src/browserfs.ts | 38 ++++++++++++++++++++++++++++++++++++++ src/downloadAndOpenFile.ts | 13 ++++++++++++- 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/README.MD b/README.MD index dcfe3fd4..b21e8c8a 100644 --- a/README.MD +++ b/README.MD @@ -141,7 +141,31 @@ Single player specific: - `?singleplayer=1` - Create empty world on load. Nothing will be saved - `?version=` - Set the version for the singleplayer world (when used with `?singleplayer=1`) - `?noSave=true` - Disable auto save on unload / disconnect / export whenever a world is loaded. Only manual save with `/save` command will work. -- `?map=` - Load the map from ZIP. You can use any url, but it must be CORS enabled. +- `?map=` - Load the map from ZIP. You can use any url, but it must be **CORS enabled**. +- `?mapDir=` - Load the map from a file descriptor. It's recommended and the fastest way to load world but requires additional setup. The file must be in the following format: + +```json +{ + "baseUrl": "", + "index": { + "level.dat": null, + "region": { + "r.-1.-1.mca": null, + "r.-1.0.mca": null, + "r.0.-1.mca": null, + "r.0.0.mca": null, + } + } +} +``` + +Note that `mapDir` also accepts base64 encoded JSON like so: +`?mapDir=data:application/json;base64,...` where `...` is the base64 encoded JSON of the index file. +In this case you must use `?mapDirBaseUrl` to specify the base URL to fetch the files from index. + +- `?mapDirBaseUrl` - See above. + + General: diff --git a/server.js b/server.js index 7adae4bb..dd1ccaa1 100644 --- a/server.js +++ b/server.js @@ -17,6 +17,7 @@ const app = express() const isProd = process.argv.includes('--prod') app.use(compression()) +// app.use(cors()) app.use(netApi({ allowOrigin: '*' })) if (!isProd) { app.use('/sounds', express.static(path.join(__dirname, './generated/sounds/'))) diff --git a/src/browserfs.ts b/src/browserfs.ts index 16691986..0c4c7664 100644 --- a/src/browserfs.ts +++ b/src/browserfs.ts @@ -433,6 +433,44 @@ export const copyFilesAsync = async (pathSrc: string, pathDest: string, fileCopi })) } +export const openWorldFromHttpDir = async (fileDescriptorUrl: string/* | undefined */, baseUrl = fileDescriptorUrl.split('/').slice(0, -1).join('/')) => { + // todo try go guess mode + let index + const file = await fetch(fileDescriptorUrl).then(async a => a.json()) + if (file.baseUrl) { + baseUrl = new URL(file.baseUrl, baseUrl).toString() + index = file.index + } else { + index = file + } + if (!index) throw new Error(`The provided mapDir file is not valid descriptor file! ${fileDescriptorUrl}`) + await new Promise(async resolve => { + browserfs.configure({ + fs: 'MountableFileSystem', + options: { + ...defaultMountablePoints, + '/world': { + fs: 'HTTPRequest', + options: { + index, + baseUrl + } + } + }, + }, (e) => { + if (e) throw e + resolve() + }) + }) + + fsState.saveLoaded = false + fsState.isReadonly = true + fsState.syncFs = false + fsState.inMemorySave = false + + await loadSave() +} + // todo rename method const openWorldZipInner = async (file: File | ArrayBuffer, name = file['name']) => { await new Promise(async resolve => { diff --git a/src/downloadAndOpenFile.ts b/src/downloadAndOpenFile.ts index 7ae8547f..49fc5c35 100644 --- a/src/downloadAndOpenFile.ts +++ b/src/downloadAndOpenFile.ts @@ -1,5 +1,5 @@ import prettyBytes from 'pretty-bytes' -import { openWorldZip } from './browserfs' +import { openWorldFromHttpDir, openWorldZip } from './browserfs' import { getResourcePackNames, installTexturePack, resourcePackState, updateTexturePackInstalledState } from './resourcePack' import { setLoadingScreenStatus } from './utils' @@ -9,6 +9,17 @@ export const getFixedFilesize = (bytes: number) => { const inner = async () => { const qs = new URLSearchParams(window.location.search) + const mapUrlDir = qs.get('mapDir') + const mapUrlDirGuess = qs.get('mapDirGuess') + const mapUrlDirBaseUrl = qs.get('mapDirBaseUrl') + if (mapUrlDir) { + await openWorldFromHttpDir(mapUrlDir, mapUrlDirBaseUrl ?? undefined) + return true + } + if (mapUrlDirGuess) { + // await openWorldFromHttpDir(undefined, mapUrlDirGuess) + return true + } let mapUrl = qs.get('map') const texturepack = qs.get('texturepack') // fixme