Skip to content

Commit 5bf52b1

Browse files
authored
Nbt rewrite (#35)
* WIP nbt rewrite, improve type safety, snbt parsing * Various stability fixes * Fix find and replace * Update deepslate * Update deepslate to 0.15.1 * Update deepslate and remove snbt and bigint utils * Add searching by snbt string (including compounds and lists) * Fix #36 add hotkeys for tag types + press A to add tag * Remove debug message * Fix floats and double serialization + remove A hotkey * Fix #42 update deepslate * Update deepslate
1 parent 5cac731 commit 5bf52b1

18 files changed

+635
-658
lines changed

package-lock.json

+18-17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"lint": "eslint . --ext .ts"
6767
},
6868
"dependencies": {
69-
"deepslate": "^0.10.0",
69+
"deepslate": "^0.17.4",
7070
"env-paths": "^2.2.1",
7171
"follow-redirects": "^1.14.8",
7272
"gl-matrix": "^3.4.3",

res/editor.css

+34-21
Original file line numberDiff line numberDiff line change
@@ -205,22 +205,22 @@ span.nbt-value {
205205
min-width: 16px;
206206
}
207207

208-
[data-icon=byte] { background-position: 0 0; }
209-
[data-icon=double] { background-position: -16px 0; }
210-
[data-icon=float] { background-position: -32px 0; }
211-
[data-icon=int] { background-position: -48px 0; }
212-
[data-icon=long] { background-position: 0 -16px; }
213-
[data-icon=short] { background-position: -16px -16px; }
214-
[data-icon=string] { background-position: -32px -16px; }
215-
[data-icon=compound] { background-position: -48px -16px; }
216-
[data-icon=list] { background-position: -32px -32px; }
217-
[data-icon=byteArray] { background-position: 0 -32px; }
218-
[data-icon=intArray] { background-position: -16px -32px; }
219-
[data-icon=longArray] { background-position: 0 -48px; }
220-
[data-icon=chunk] { background-position: -16px -48px; }
221-
[data-icon=number] { background-position: -32px -48px; }
222-
[data-icon=null] { background-position: -48px -48px; }
223-
[data-icon=any] {
208+
[data-icon=Byte] { background-position: 0 0; }
209+
[data-icon=Double] { background-position: -16px 0; }
210+
[data-icon=Float] { background-position: -32px 0; }
211+
[data-icon=Int] { background-position: -48px 0; }
212+
[data-icon=Long] { background-position: 0 -16px; }
213+
[data-icon=Short] { background-position: -16px -16px; }
214+
[data-icon=String] { background-position: -32px -16px; }
215+
[data-icon=Compound] { background-position: -48px -16px; }
216+
[data-icon=List] { background-position: -32px -32px; }
217+
[data-icon=ByteArray] { background-position: 0 -32px; }
218+
[data-icon=IntArray] { background-position: -16px -32px; }
219+
[data-icon=LongArray] { background-position: 0 -48px; }
220+
[data-icon=Chunk] { background-position: -16px -48px; }
221+
[data-icon=Number] { background-position: -32px -48px; }
222+
[data-icon=Null] { background-position: -48px -48px; }
223+
[data-icon=Any] {
224224
background: var(--vscode-input-background);
225225
}
226226

@@ -613,7 +613,7 @@ span.nbt-value {
613613
aspect-ratio: 1/1;
614614
background-color: var(--vscode-sideBar-background);
615615
color: var(--vscode-sideBar-foreground);
616-
border: 2px solid var(--vscode-sideBarSectionHeader-border);
616+
border: 1px solid var(--vscode-sideBarSectionHeader-border);
617617
padding: 1px;
618618
display: flex;
619619
justify-content: center;
@@ -624,24 +624,37 @@ span.nbt-value {
624624
cursor: pointer;
625625
}
626626

627-
.region-map-chunk:not(:nth-child(-n+32)) {
628-
border-top: none;
627+
.region-map-chunk:nth-child(-n+32) {
628+
border-top-width: 2px;
629+
}
630+
631+
.region-map-chunk:nth-child(n+993) {
632+
border-bottom-width: 2px;
633+
}
634+
635+
.region-map-chunk:nth-child(32n+1) {
636+
border-left-width: 2px;
629637
}
630638

631-
.region-map-chunk:not(:nth-child(32n+1)) {
632-
border-left: none;
639+
.region-map-chunk:nth-child(32n) {
640+
border-right-width: 2px;
633641
}
634642

635643
.region-map-chunk.empty {
636644
background-color: transparent;
637645
color: transparent;
638646
cursor: initial;
647+
user-select: none;
639648
}
640649

641650
.region-map-chunk.loaded {
642651
background-color: var(--vscode-menu-selectionBackground);
643652
}
644653

654+
.region-map-chunk.invalid {
655+
background-color: var(--vscode-inputValidation-errorBackground);
656+
}
657+
645658
.hidden {
646659
display: none !important;
647660
}

rollup.config.js

+9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default defineConfig([
1919
commonjs(),
2020
typescript(),
2121
],
22+
onwarn,
2223
},
2324
{
2425
input: 'src/editor/Editor.ts',
@@ -35,5 +36,13 @@ export default defineConfig([
3536
commonjs(),
3637
typescript(),
3738
],
39+
onwarn,
3840
},
3941
])
42+
43+
function onwarn(warning) {
44+
if (warning.code === 'CIRCULAR_DEPENDENCY') {
45+
return
46+
}
47+
console.warn(`(!) ${warning.message}`)
48+
}

src/NbtDocument.ts

+23-55
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { NbtChunk } from 'deepslate'
2-
import { getOptional, getTag, loadChunk, readNbt, readRegion, saveChunk, writeNbt, writeRegion } from 'deepslate'
2+
import { NbtFile, NbtRegion, NbtType } from 'deepslate'
33
import * as vscode from 'vscode'
44
import { applyEdit, reverseEdit } from './common/Operations'
5-
import type { Logger, NbtEdit, NbtFile } from './common/types'
5+
import type { Logger, NbtEdit } from './common/types'
66
import { Disposable } from './dispose'
77

88
export class NbtDocument extends Disposable implements vscode.CustomDocument {
@@ -18,41 +18,31 @@ export class NbtDocument extends Disposable implements vscode.CustomDocument {
1818
return new NbtDocument(uri, fileData, logger)
1919
}
2020

21-
private static async readFile(uri: vscode.Uri, logger: Logger): Promise<NbtFile> {
21+
private static async readFile(uri: vscode.Uri, logger: Logger): Promise<NbtFile | NbtRegion> {
2222
const array = await vscode.workspace.fs.readFile(uri)
2323

2424
logger.info(`Read file [length=${array.length}, scheme=${uri.scheme}, extension=${uri.path.match(/(?:\.([^.]+))?$/)?.[1]}]`)
2525

2626
if (uri.scheme === 'git' && array.length === 0) {
27-
return {
28-
region: false,
29-
name: '',
30-
value: {},
31-
}
27+
return NbtFile.create()
3228
}
3329

3430
if (uri.fsPath.endsWith('.mca')) {
35-
return {
36-
region: true,
37-
chunks: readRegion(array),
38-
}
31+
return NbtRegion.read(array)
3932
}
4033

4134
const littleEndian = uri.fsPath.endsWith('.mcstructure')
42-
const result = readNbt(array, { littleEndian })
35+
const file = NbtFile.read(array, { littleEndian })
4336

44-
logger.info(`Parsed NBT [compression=${result.compression ?? 'none'}, littleEndian=${result.littleEndian ?? false}, bedrockHeader=${result.bedrockHeader ?? 'none'}]`)
37+
logger.info(`Parsed NBT [compression=${file.compression ?? 'none'}, littleEndian=${file.littleEndian ?? false}, bedrockHeader=${file.bedrockHeader ?? 'none'}]`)
4538

46-
return {
47-
region: false,
48-
...result,
49-
}
39+
return file
5040
}
5141

5242

5343
private readonly _uri: vscode.Uri
5444

55-
private _documentData: NbtFile
45+
private _documentData: NbtFile | NbtRegion
5646
private readonly _isStructure: boolean
5747
private readonly _isMap: boolean
5848
private readonly _isReadOnly: boolean
@@ -61,7 +51,7 @@ export class NbtDocument extends Disposable implements vscode.CustomDocument {
6151

6252
private constructor(
6353
uri: vscode.Uri,
64-
initialContent: NbtFile,
54+
initialContent: NbtFile | NbtRegion,
6555
private readonly logger: Logger,
6656
) {
6757
super()
@@ -85,13 +75,11 @@ export class NbtDocument extends Disposable implements vscode.CustomDocument {
8575

8676
public get dataVersion() {
8777
const file = this._documentData
88-
if (file.region) {
89-
const firstChunk = file.chunks.find(c => c.data || c.nbt)
90-
if (!firstChunk) return undefined
91-
loadChunk(firstChunk)
92-
return getOptional(() => getTag(firstChunk.nbt!.value, 'DataVersion', 'int'), undefined)
78+
if (file instanceof NbtRegion) {
79+
const firstChunk = file.getFirstChunk()
80+
return firstChunk?.getRoot().getNumber('DataVersion') ?? 0
9381
} else {
94-
return getOptional(() => getTag(file.value, 'DataVersion', 'int'), undefined)
82+
return file.root.getNumber('DataVersion') ?? 0
9583
}
9684
}
9785

@@ -140,30 +128,26 @@ export class NbtDocument extends Disposable implements vscode.CustomDocument {
140128
}
141129

142130
private isStructureData() {
143-
if (this._documentData.region) return false
144-
const root = this._documentData.value
145-
return root['size']?.type === 'list'
146-
&& root['size'].value.type === 'int'
147-
&& root['size'].value.value.length === 3
148-
&& root['blocks']?.type === 'list'
149-
&& root['palette']?.type === 'list'
131+
if (this._documentData instanceof NbtRegion) return false
132+
const root = this._documentData.root
133+
return root.hasList('size', NbtType.Int, 3)
134+
&& root.hasList('blocks') && root.hasList('palette')
150135
}
151136

152137
private isMapData() {
153138
return this._uri.fsPath.match(/(?:\\|\/)map_\d+\.dat$/) !== null
154139
}
155140

156141
async getChunkData(x: number, z: number): Promise<NbtChunk> {
157-
if (!this._documentData.region) {
142+
if (!(this._documentData instanceof NbtRegion)) {
158143
throw new Error('File is not a region file')
159144
}
160145

161-
const chunks = this._documentData.chunks
162-
const chunk = chunks.find(c => c.x === x && c.z === z)
146+
const chunk = this._documentData.findChunk(x, z)
163147
if (!chunk) {
164148
throw new Error(`Cannot find chunk [${x}, ${z}]`)
165149
}
166-
return loadChunk(chunk)
150+
return chunk
167151
}
168152

169153
async save(cancellation: vscode.CancellationToken): Promise<void> {
@@ -184,16 +168,7 @@ export class NbtDocument extends Disposable implements vscode.CustomDocument {
184168
return
185169
}
186170

187-
if (nbtFile.region) {
188-
nbtFile.chunks.filter(c => c.dirty).forEach(chunk => {
189-
saveChunk(chunk)
190-
chunk.dirty = false
191-
})
192-
}
193-
194-
const fileData = nbtFile.region
195-
? writeRegion(nbtFile.chunks)
196-
: writeNbt(nbtFile.value, nbtFile)
171+
const fileData = nbtFile.write()
197172

198173
await vscode.workspace.fs.writeFile(targetResource, fileData)
199174
}
@@ -202,14 +177,7 @@ export class NbtDocument extends Disposable implements vscode.CustomDocument {
202177
const diskContent = await NbtDocument.readFile(this.uri, this.logger)
203178
this._documentData = diskContent
204179
this._edits = this._savedEdits
205-
this._onDidChangeDocument.fire({
206-
ops: [{
207-
type: 'set',
208-
path: [],
209-
old: null,
210-
new: this._documentData,
211-
}],
212-
})
180+
// TODO: notify listeners that document has reset
213181
}
214182

215183
async backup(destination: vscode.Uri, cancellation: vscode.CancellationToken): Promise<vscode.CustomDocumentBackup> {

0 commit comments

Comments
 (0)