Skip to content

Commit 397a7ba

Browse files
authored
Merge pull request #473 from OKTW-Network/opt-chunk
Restore Chunk IO Patch
2 parents 16251b3 + 143ec32 commit 397a7ba

File tree

8 files changed

+502
-5
lines changed

8 files changed

+502
-5
lines changed

docker

Submodule docker updated 1 file

src/main/java/one/oktw/galaxy/mixin/interfaces/RegionFileInputStream.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* OKTW Galaxy Project
3-
* Copyright (C) 2018-2021
3+
* Copyright (C) 2018-2025
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU Affero General Public License as published
@@ -24,5 +24,5 @@
2424
import java.io.IOException;
2525

2626
public interface RegionFileInputStream {
27-
DataInputStream getChunkInputStreamNoSync(ChunkPos pos) throws IOException;
27+
DataInputStream galaxy$getChunkInputStreamNoSync(ChunkPos pos) throws IOException;
2828
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* OKTW Galaxy Project
3+
* Copyright (C) 2018-2025
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published
7+
* by the Free Software Foundation, either version 3 of the License, or
8+
* any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package one.oktw.galaxy.mixin.tweak;
20+
21+
import net.minecraft.util.math.ChunkPos;
22+
import net.minecraft.world.storage.RegionBasedStorage;
23+
import net.minecraft.world.storage.RegionFile;
24+
import one.oktw.galaxy.mixin.interfaces.RegionFileInputStream;
25+
import org.spongepowered.asm.mixin.Mixin;
26+
import org.spongepowered.asm.mixin.Unique;
27+
import org.spongepowered.asm.mixin.injection.At;
28+
import org.spongepowered.asm.mixin.injection.Inject;
29+
import org.spongepowered.asm.mixin.injection.Redirect;
30+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
31+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
32+
33+
import java.io.DataInputStream;
34+
import java.io.IOException;
35+
import java.util.concurrent.locks.ReentrantLock;
36+
37+
@Mixin(RegionBasedStorage.class)
38+
public abstract class MixinAsyncChunk_RegionBasedStorage {
39+
@Unique
40+
private final ReentrantLock lock = new ReentrantLock();
41+
42+
@Inject(method = "getRegionFile", at = @At("HEAD"))
43+
private void readLock(ChunkPos pos, CallbackInfoReturnable<RegionFile> cir) {
44+
lock.lock();
45+
}
46+
47+
@Inject(method = "getRegionFile", at = @At("RETURN"))
48+
private void readUnlock(ChunkPos pos, CallbackInfoReturnable<RegionFile> cir) {
49+
lock.unlock();
50+
}
51+
52+
@Inject(method = "close", at = @At("HEAD"))
53+
private void closeLock(CallbackInfo ci) {
54+
lock.lock();
55+
}
56+
57+
@Inject(method = "close", at = @At("RETURN"))
58+
private void closeUnlock(CallbackInfo ci) {
59+
lock.unlock();
60+
}
61+
62+
@Inject(method = "sync", at = @At("HEAD"))
63+
private void syncLock(CallbackInfo ci) {
64+
lock.lock();
65+
}
66+
67+
@Inject(method = "sync", at = @At("RETURN"))
68+
private void syncUnlock(CallbackInfo ci) {
69+
lock.unlock();
70+
}
71+
72+
@Redirect(method = "getTagAt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/storage/RegionFile;getChunkInputStream(Lnet/minecraft/util/math/ChunkPos;)Ljava/io/DataInputStream;"))
73+
private DataInputStream overwriteGetInputStream(RegionFile regionFile, ChunkPos pos) throws IOException {
74+
return ((RegionFileInputStream) regionFile).galaxy$getChunkInputStreamNoSync(pos);
75+
}
76+
77+
@Redirect(method = "scanChunk", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/storage/RegionFile;getChunkInputStream(Lnet/minecraft/util/math/ChunkPos;)Ljava/io/DataInputStream;"))
78+
private DataInputStream overwriteGetInputStream2(RegionFile regionFile, ChunkPos pos) throws IOException {
79+
return ((RegionFileInputStream) regionFile).galaxy$getChunkInputStreamNoSync(pos);
80+
}
81+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* OKTW Galaxy Project
3+
* Copyright (C) 2018-2025
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published
7+
* by the Free Software Foundation, either version 3 of the License, or
8+
* any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package one.oktw.galaxy.mixin.tweak;
20+
21+
import net.minecraft.util.math.ChunkPos;
22+
import net.minecraft.util.profiling.jfr.FlightProfiler;
23+
import net.minecraft.world.storage.ChunkCompressionFormat;
24+
import net.minecraft.world.storage.RegionFile;
25+
import net.minecraft.world.storage.StorageKey;
26+
import one.oktw.galaxy.mixin.interfaces.RegionFileInputStream;
27+
import org.jetbrains.annotations.Nullable;
28+
import org.slf4j.Logger;
29+
import org.spongepowered.asm.mixin.Final;
30+
import org.spongepowered.asm.mixin.Mixin;
31+
import org.spongepowered.asm.mixin.Shadow;
32+
import org.spongepowered.asm.mixin.Unique;
33+
import org.spongepowered.asm.mixin.injection.At;
34+
import org.spongepowered.asm.mixin.injection.Inject;
35+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
36+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
37+
38+
import java.io.ByteArrayInputStream;
39+
import java.io.DataInputStream;
40+
import java.io.IOException;
41+
import java.io.InputStream;
42+
import java.nio.ByteBuffer;
43+
import java.nio.channels.FileChannel;
44+
import java.util.concurrent.locks.ReentrantLock;
45+
46+
@Mixin(RegionFile.class)
47+
public abstract class MixinAsyncChunk_RegionFile implements RegionFileInputStream {
48+
@Unique
49+
private final ReentrantLock lock = new ReentrantLock();
50+
51+
@Shadow
52+
@Final
53+
private static Logger LOGGER;
54+
55+
@Shadow
56+
@Final
57+
private FileChannel channel;
58+
59+
@Shadow
60+
@Final
61+
StorageKey storageKey;
62+
63+
@Shadow
64+
@Final
65+
ChunkCompressionFormat compressionFormat;
66+
67+
@Shadow
68+
protected abstract int getSectorData(ChunkPos pos);
69+
70+
@Shadow
71+
private static int getOffset(int sectorData) {
72+
return 0;
73+
}
74+
75+
@Shadow
76+
private static int getSize(int sectorData) {
77+
return 0;
78+
}
79+
80+
@Shadow
81+
private static boolean hasChunkStreamVersionId(byte b) {
82+
return false;
83+
}
84+
85+
@Shadow
86+
private static byte getChunkStreamVersionId(byte b) {
87+
return 0;
88+
}
89+
90+
@Shadow
91+
private static ByteArrayInputStream getInputStream(ByteBuffer buffer, int length) {
92+
return null;
93+
}
94+
95+
@Shadow
96+
@Nullable
97+
protected abstract DataInputStream getInputStream(ChunkPos chunkPos, byte b) throws IOException;
98+
99+
@Shadow
100+
@Nullable
101+
protected abstract DataInputStream decompress(ChunkPos chunkPos, byte b, InputStream inputStream) throws IOException;
102+
103+
@Inject(method = "delete", at = @At("HEAD"))
104+
private void deleteLock(ChunkPos chunkPos, CallbackInfo ci) {
105+
lock.lock();
106+
}
107+
108+
@Inject(method = "delete", at = @At("RETURN"))
109+
private void deleteUnlock(ChunkPos chunkPos, CallbackInfo ci) {
110+
lock.unlock();
111+
}
112+
113+
@Inject(method = "writeChunk", at = @At("HEAD"))
114+
private void writeChunkLock(ChunkPos pos, ByteBuffer byteBuffer, CallbackInfo ci) {
115+
lock.lock();
116+
}
117+
118+
@Inject(method = "writeChunk", at = @At("RETURN"))
119+
private void writeChunkUnlock(ChunkPos pos, ByteBuffer byteBuffer, CallbackInfo ci) {
120+
lock.unlock();
121+
}
122+
123+
@Inject(method = "getSectorData", at = @At("HEAD"))
124+
private void getSectorDataLock(ChunkPos pos, CallbackInfoReturnable<Integer> cir) {
125+
lock.lock();
126+
}
127+
128+
@Inject(method = "getSectorData", at = @At("RETURN"))
129+
private void getSectorDataUnlock(ChunkPos pos, CallbackInfoReturnable<Integer> cir) {
130+
lock.unlock();
131+
}
132+
133+
// Remove synchronized.
134+
@Override
135+
public DataInputStream galaxy$getChunkInputStreamNoSync(ChunkPos pos) throws IOException {
136+
int i = getSectorData(pos);
137+
if (i == 0) return null;
138+
int start = getOffset(i);
139+
int count = getSize(i);
140+
int length = count * 4096;
141+
ByteBuffer byteBuffer = ByteBuffer.allocate(length);
142+
channel.read(byteBuffer, start * 4096L);
143+
byteBuffer.flip();
144+
if (byteBuffer.remaining() < 5) {
145+
LOGGER.error("Chunk {} header is truncated: expected {} but read {}", pos, length, byteBuffer.remaining());
146+
return null;
147+
}
148+
int m = byteBuffer.getInt();
149+
byte b = byteBuffer.get();
150+
if (m == 0) {
151+
LOGGER.warn("Chunk {} is allocated, but stream is missing", pos);
152+
return null;
153+
}
154+
int n = m - 1;
155+
if (hasChunkStreamVersionId(b)) {
156+
if (n != 0) LOGGER.warn("Chunk has both internal and external streams");
157+
return getInputStream(pos, getChunkStreamVersionId(b));
158+
}
159+
if (n > byteBuffer.remaining()) {
160+
LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", pos, n, byteBuffer.remaining());
161+
return null;
162+
}
163+
if (n < 0) {
164+
LOGGER.error("Declared size {} of chunk {} is negative", m, pos);
165+
return null;
166+
}
167+
FlightProfiler.INSTANCE.onChunkRegionRead(storageKey, pos, compressionFormat, n);
168+
return decompress(pos, b, getInputStream(byteBuffer, n));
169+
}
170+
}

0 commit comments

Comments
 (0)