feat: implement fast world loading with file descriptor & http backend! (#182)

This commit is contained in:
Vitaly 2024-08-19 14:01:13 +03:00 committed by GitHub
commit 24fd4d4fc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 76 additions and 2 deletions

View file

@ -141,7 +141,31 @@ Single player specific:
- `?singleplayer=1` - Create empty world on load. Nothing will be saved
- `?version=<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=<map_url>` - Load the map from ZIP. You can use any url, but it must be CORS enabled.
- `?map=<map_url>` - Load the map from ZIP. You can use any url, but it must be **CORS enabled**.
- `?mapDir=<index_file_url>` - 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": "<url>",
"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.
<!-- - `?mapDirGuess=<base_url>` - Load the map from the provided URL and paths will be guessed with a few additional fetch requests. -->
General:

View file

@ -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/')))

View file

@ -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<void>(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<void>(async resolve => {

View file

@ -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