Update TraceRepository to save quite a bit smaller traces

This commit is contained in:
2025-07-02 09:16:48 +02:00
parent d04939fb2c
commit 95a97aed93
4 changed files with 84 additions and 74 deletions
@@ -50,12 +50,6 @@ public class Trace {
@Getter @Getter
private final File recordsSaveFile; private final File recordsSaveFile;
/**
* File the metadata are saved in
*/
@Getter
private final File metadataSaveFile;
/** /**
* Region the trace was recorded in * Region the trace was recorded in
*/ */
@@ -75,7 +69,7 @@ public class Trace {
@Setter @Setter
@Getter @Getter
private int recordsCount; private int tntIdCount;
/** /**
* A map of all REntityServers rendering this trace * A map of all REntityServers rendering this trace
@@ -95,21 +89,19 @@ public class Trace {
this.date = new Date(); this.date = new Date();
records = new SoftReference<>(recordList); records = new SoftReference<>(recordList);
recordsSaveFile = new File(TraceRepository.tracesFolder, uuid + ".records"); recordsSaveFile = new File(TraceRepository.tracesFolder, uuid + ".records");
metadataSaveFile = new File(TraceRepository.tracesFolder, uuid + ".meta");
} }
/** /**
* Constructor for deserialising a trace from the file system * Constructor for deserialising a trace from the file system
*/ */
@SneakyThrows @SneakyThrows
protected Trace(UUID uuid, Region region, Date date, File metadataFile, File recordsFile, int recordsCount) { protected Trace(UUID uuid, Region region, Date date, File recordsFile, int tntIdCount) {
this.metadataSaveFile = metadataFile;
recordsSaveFile = recordsFile; recordsSaveFile = recordsFile;
this.uuid = uuid; this.uuid = uuid;
this.region = region; this.region = region;
this.date = date; this.date = date;
this.records = new SoftReference<>(null); this.records = new SoftReference<>(null);
this.recordsCount = recordsCount; this.tntIdCount = tntIdCount;
} }
/** /**
@@ -311,7 +303,7 @@ public class Trace {
", region=" + region + ", region=" + region +
", creationTime=" + date + ", creationTime=" + date +
", recordsSaveFile=" + recordsSaveFile.getName() + ", recordsSaveFile=" + recordsSaveFile.getName() +
", recordCount=" + recordsCount + ", tntCount=" + tntIdCount +
", records=" + getRecords() + ", records=" + getRecords() +
'}'; '}';
} }
@@ -57,19 +57,27 @@ public class TraceManager implements Listener {
if (traceFiles == null) if (traceFiles == null)
return; return;
boolean hasMetaFiles = false;
for (File traceFile : traceFiles) { for (File traceFile : traceFiles) {
if (traceFile.getName().contains(".meta")) if (traceFile.getName().contains(".meta")) {
continue; hasMetaFiles = true;
if (TraceRepository.getVersion(traceFile) == TraceRepository.SERIALISATION_VERSION) {
add(TraceRepository.readTrace(traceFile));
} else {
String uuid = traceFile.getName().replace(".records", "");
new File(tracesFolder, uuid + ".records").deleteOnExit();
new File(tracesFolder, uuid + ".meta").deleteOnExit();
} }
}
if (hasMetaFiles) {
for (File traceFile : traceFiles) {
traceFile.delete();
}
traceFiles = new File[0];
}
// TODO: Cleanup all traces if a .meta is present!
for (File traceFile : traceFiles) {
Trace trace = TraceRepository.readTrace(traceFile);
if (trace == null) {
traceFile.delete();
continue;
}
add(trace);
} }
} }
@@ -152,7 +160,6 @@ public class TraceManager implements Listener {
if (traceId == null) throw new RuntimeException("Trace not found while trying to remove see (c978eb98-b0b2-4009-91d8-acfa34e2831a)"); if (traceId == null) throw new RuntimeException("Trace not found while trying to remove see (c978eb98-b0b2-4009-91d8-acfa34e2831a)");
traces.remove(traceId); traces.remove(traceId);
trace.hide(); trace.hide();
trace.getMetadataSaveFile().delete();
trace.getRecordsSaveFile().delete(); trace.getRecordsSaveFile().delete();
} }
@@ -172,7 +179,6 @@ public class TraceManager implements Listener {
tracesByRegion.getOrDefault(region, new HashMap<>()) tracesByRegion.getOrDefault(region, new HashMap<>())
.forEach((i, trace) -> { .forEach((i, trace) -> {
if (trace.getRegion() != region) return; if (trace.getRegion() != region) return;
trace.getMetadataSaveFile().delete();
trace.getRecordsSaveFile().delete(); trace.getRecordsSaveFile().delete();
}); });
tracesByRegion.getOrDefault(region, new HashMap<>()).clear(); tracesByRegion.getOrDefault(region, new HashMap<>()).clear();
@@ -65,7 +65,7 @@ public class TraceRecordingWrapper {
TraceManager.instance.showPartial(trace, recordsToAdd); TraceManager.instance.showPartial(trace, recordsToAdd);
recordList.addAll(recordsToAdd); recordList.addAll(recordsToAdd);
trace.setRecordsCount(recordList.size()); trace.setTntIdCount((int) recordList.stream().map(TNTPoint::getTntId).distinct().count());
recordsToAdd.clear(); recordsToAdd.clear();
} }
@@ -9,60 +9,56 @@ import org.bukkit.util.Vector;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class TraceRepository { public class TraceRepository {
/** /**
* Increment this when changing serialisation format * Increment this when changing serialisation format
*/ */
public static final int SERIALISATION_VERSION = 1; public static final int SERIALISATION_VERSION = 2;
public static final int WRITE_TICK_DATA = 0x01;
public static final int EXPLOSION = 0x02;
public static final int IN_WATER = 0x04;
public static final int AFTER_FIRST_EXPLOSION = 0x08;
public static final int DESTROYED_BUILD_AREA = 0x10;
public static final int DESTROYED_TEST_BLOCK = 0x20;
public static File tracesFolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "traces"); public static File tracesFolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "traces");
@SneakyThrows
protected static int getVersion(File metadataFile) {
@Cleanup
ObjectInputStream reader = new ObjectInputStream(new FileInputStream(metadataFile));
reader.readUTF();
reader.readUTF();
reader.readObject();
try {
int version = reader.readInt();
return version;
} catch (EOFException e) {
return 0;
}
}
@SneakyThrows @SneakyThrows
public static Trace readTrace(File recordsFile) { public static Trace readTrace(File recordsFile) {
@Cleanup @Cleanup
ObjectInputStream reader = new ObjectInputStream(new FileInputStream(recordsFile)); ObjectInputStream reader = new ObjectInputStream(new GZIPInputStream(new FileInputStream(recordsFile)));
UUID uuid = UUID.fromString(reader.readUTF()); UUID uuid = UUID.fromString(reader.readUTF());
Region region = Region.getREGION_MAP().get(reader.readUTF()); Region region = Region.getREGION_MAP().get(reader.readUTF());
Date date = (Date) reader.readObject(); Date date = (Date) reader.readObject();
int serialisationVersion = reader.readInt(); int serialisationVersion = reader.readInt();
int recordsCount = reader.readInt(); if (serialisationVersion != SERIALISATION_VERSION) {
return null;
}
int tntIdCount = reader.readInt();
return new Trace(uuid, region, date, recordsFile, recordsFile, recordsCount); return new Trace(uuid, region, date, recordsFile, tntIdCount);
} }
@SneakyThrows @SneakyThrows
protected static void writeTrace(Trace trace, List<TNTPoint> records) { protected static void writeTrace(Trace trace, List<TNTPoint> records) {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(trace.getMetadataSaveFile())); ObjectOutputStream outputStream = new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(trace.getRecordsSaveFile())));
outputStream.writeUTF(trace.getUuid().toString()); outputStream.writeUTF(trace.getUuid().toString());
outputStream.writeUTF(trace.getRegion().getName()); outputStream.writeUTF(trace.getRegion().getName());
outputStream.writeObject(trace.getDate()); outputStream.writeObject(trace.getDate());
outputStream.writeInt(SERIALISATION_VERSION + 1); outputStream.writeInt(SERIALISATION_VERSION);
outputStream.writeInt(records.size());
Map<Integer, List<TNTPoint>> pointsByTNTId = new HashMap<>(); Map<Integer, List<TNTPoint>> pointsByTNTId = new HashMap<>();
records.forEach(tntPoint -> { records.forEach(tntPoint -> {
pointsByTNTId.computeIfAbsent(tntPoint.getTntId(), integer -> new ArrayList<>()).add(tntPoint); pointsByTNTId.computeIfAbsent(tntPoint.getTntId(), integer -> new ArrayList<>()).add(tntPoint);
}); });
outputStream.writeInt(pointsByTNTId.size());
for (Map.Entry<Integer, List<TNTPoint>> entry : pointsByTNTId.entrySet()) { for (Map.Entry<Integer, List<TNTPoint>> entry : pointsByTNTId.entrySet()) {
outputStream.writeInt(entry.getKey()); outputStream.writeInt(entry.getKey());
outputStream.write(entry.getValue().size()); outputStream.writeInt(entry.getValue().size());
for (int i = 0; i < entry.getValue().size(); i++) { for (int i = 0; i < entry.getValue().size(); i++) {
TNTPoint current = entry.getValue().get(i); TNTPoint current = entry.getValue().get(i);
@@ -89,12 +85,12 @@ public class TraceRepository {
@SneakyThrows @SneakyThrows
private static void writeTNTPoint(ObjectOutputStream outputStream, TNTPoint tntPoint, boolean writeTickData) { private static void writeTNTPoint(ObjectOutputStream outputStream, TNTPoint tntPoint, boolean writeTickData) {
byte data = 0; byte data = 0;
if (writeTickData) data |= 0x01; if (writeTickData) data |= WRITE_TICK_DATA;
if (tntPoint.isExplosion()) data |= 0x02; if (tntPoint.isExplosion()) data |= EXPLOSION;
if (tntPoint.isInWater()) data |= 0x04; if (tntPoint.isInWater()) data |= IN_WATER;
if (tntPoint.isAfterFirstExplosion()) data |= 0x08; if (tntPoint.isAfterFirstExplosion()) data |= AFTER_FIRST_EXPLOSION;
if (tntPoint.isDestroyedBuildArea()) data |= 0x10; if (tntPoint.isDestroyedBuildArea()) data |= DESTROYED_BUILD_AREA;
if (tntPoint.isDestroyedTestBlock()) data |= 0x20; if (tntPoint.isDestroyedTestBlock()) data |= DESTROYED_TEST_BLOCK;
outputStream.write(data); outputStream.write(data);
if (writeTickData) { if (writeTickData) {
@@ -114,16 +110,24 @@ public class TraceRepository {
} }
@SneakyThrows @SneakyThrows
protected static TNTPoint readTraceRecord(DataInputStream objectInput) { protected static TNTPoint readTraceRecord(int tntId, TNTPoint last, ObjectInputStream objectInput) {
int tntId = objectInput.readInt(); int data = objectInput.read();
boolean explosion = objectInput.readBoolean(); boolean explosion = (data & EXPLOSION) > 0;
boolean inWater = objectInput.readBoolean(); boolean inWater = (data & IN_WATER) > 0;
boolean afterFirstExplosion = objectInput.readBoolean(); boolean afterFirstExplosion = (data & AFTER_FIRST_EXPLOSION) > 0;
boolean destroyedBuildArea = objectInput.readBoolean(); boolean destroyedBuildArea = (data & DESTROYED_BUILD_AREA) > 0;
boolean destroyedTestBlock = objectInput.readBoolean(); boolean destroyedTestBlock = (data & DESTROYED_TEST_BLOCK) > 0;
long ticksSinceStart = objectInput.readLong();
int fuse = objectInput.readInt(); long ticksSinceStart;
int fuse;
if ((data & WRITE_TICK_DATA) > 0) {
ticksSinceStart = objectInput.readLong();
fuse = objectInput.readInt();
} else {
ticksSinceStart = last.getTicksSinceStart() + 1;
fuse = last.getFuse() - 1;
}
double locX = objectInput.readDouble(); double locX = objectInput.readDouble();
double locY = objectInput.readDouble(); double locY = objectInput.readDouble();
@@ -142,21 +146,29 @@ public class TraceRepository {
protected static List<TNTPoint> readTraceRecords(Trace trace) { protected static List<TNTPoint> readTraceRecords(Trace trace) {
File recordsFile = trace.getRecordsSaveFile(); File recordsFile = trace.getRecordsSaveFile();
@Cleanup @Cleanup
DataInputStream inputStream = new DataInputStream(new FileInputStream(recordsFile)); ObjectInputStream inputStream = new ObjectInputStream(new GZIPInputStream(new FileInputStream(recordsFile)));
inputStream.readUTF();
inputStream.readUTF();
inputStream.readObject();
inputStream.readInt();
inputStream.readInt();
List<TNTPoint> records = new ArrayList<>(); List<TNTPoint> records = new ArrayList<>();
for (int i = 0; i < trace.getRecordsCount(); i++) {
records.add(readTraceRecord(inputStream));
}
Map<Integer, List<TNTPoint>> histories = new HashMap<>(); Map<Integer, List<TNTPoint>> histories = new HashMap<>();
for (TNTPoint record : records) { for (int i = 0; i < trace.getTntIdCount(); i++) {
int tntId = record.getTntId(); int tntId = inputStream.readInt();
List<TNTPoint> history = histories.computeIfAbsent(tntId, id -> new ArrayList<>()); int size = inputStream.readInt();
history.add(record); List<TNTPoint> points = histories.computeIfAbsent(tntId, id -> new ArrayList<>());
record.setHistory(history);
}
TNTPoint last = null;
for (int j = 0; j < size; j++) {
TNTPoint point = readTraceRecord(tntId, last, inputStream);
point.setHistory(points);
points.add(point);
last = point;
records.add(point);
}
}
return records; return records;
} }
} }