package org.jrenner.fps;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.NumberUtils;
import org.jrenner.fps.utils.Compression;
import org.jrenner.fps.utils.ObjectSize;
import java.io.Serializable;
/** an experimental class that could be used to record and playback gameplay */
public class Recorder implements Serializable {
private Array<Snapshot> history = new Array<>(512);
private int recordIntervalFrames;
private boolean recording;
public Recorder(int frameInterval) {
recordIntervalFrames = frameInterval;
Array<Snapshot> loaded = load();
if (loaded != null) history = loaded;
}
public void update(Vector3 pos, Vector3 velocity, Quaternion rotation) {
if (Main.frame % recordIntervalFrames != 0) return;
Snapshot snap = new Snapshot();
System.out.println("Snapshot: " + ObjectSize.getObjectSize(snap));
snap.position.set(pos);
//snap.velocity.set(velocity);
snap.rotation.set(rotation);
history.add(snap);
}
public boolean isRecording() {
return recording;
}
public int getFrameInterval() {
return recordIntervalFrames;
}
public void begin() {
history.clear();
recording = true;
}
public void end() {
recording = false;
}
public Array<Snapshot> getRecording() {
return new Array<>(history);
}
boolean useJson = false;
public void write() {
if (useJson) {
writeJson();
} else {
writeBytes();
}
}
private static final String demoDir = ".fps-demo/";
public Array<Snapshot> load() {
if (useJson) {
return loadJson();
} else {
return loadBytes();
}
}
private void writeBytes() {
FileHandle fh = Gdx.files.external(demoDir + "history.dat");
byte[] bytes = new byte[Snapshot.SNAPSHOT_SIZE * history.size];
int idx = 0;
for (Snapshot snap : history) {
idx = snap.serialize(bytes, idx);
}
System.out.println("finished write, bytes length: " + bytes.length + ", idx: " + idx);
fh.writeBytes(bytes, false);
}
private Array<Snapshot> loadBytes() {
String filename = "history.dat";
FileHandle histFile = Gdx.files.external(demoDir + filename);
if (!histFile.exists()) {
Log.error("playback history file doesn't exist, skip loading: " + filename);
return null;
}
byte[] bytes = histFile.readBytes();
int len = bytes.length;
Log.debug("history files bytes: " + len);
if (len % Snapshot.SNAPSHOT_SIZE != 0) {
Log.error("playback history length error, expected divisible by: " + Snapshot.SNAPSHOT_SIZE + ", remainder: " + len % Snapshot.SNAPSHOT_SIZE);
return null;
}
Array<Snapshot> snapshots = new Array<>();
int bytesProcessed = 0;
while (bytesProcessed < len) {
snapshots.add(Snapshot.deserialize(bytes, bytesProcessed));
bytesProcessed += Snapshot.SNAPSHOT_SIZE;
}
return snapshots;
}
private void writeJson() {
Json json = new Json();
String data = json.toJson(history);
FileHandle fh = Gdx.files.external(demoDir + "history.json.gz");
byte[] bytes = Compression.writeCompressedString(data);
if (bytes == null) {
Log.error("Could not write compressed playback (JSON)");
} else {
int byteCount = bytes.length;
fh.writeBytes(bytes, false);
Log.debug("Wrote compressed playback: " + byteCount + " bytes");
}
}
private Array<Snapshot> loadJson() {
Json json = new Json();
String filename = demoDir + "history.json.gz";
FileHandle histFile = Gdx.files.external(filename);
if (!histFile.exists()) {
Log.error("playback history file doesn't exist, skip loading: " + filename);
return null;
}
byte[] bytes = histFile.readBytes();
String data = Compression.decompressToString(bytes);
System.out.println(data);
return (Array<Snapshot>) json.fromJson(Array.class, data);
}
public static class Snapshot implements Serializable {
public Vector3 position = new Vector3();
public Quaternion rotation = new Quaternion();
//public Vector3 velocity = new Vector3();
public static final int SNAPSHOT_SIZE = (4 + 3 + 3) * 4; // (pos + rot + vel) * 4 bytes per float
public static Snapshot deserialize(byte[] bytes, int idx) {
Snapshot snap = new Snapshot();
int i = idx;
// position
snap.position.x = Tools.readFloatFromBytes(bytes, i);
i += 4;
snap.position.y = Tools.readFloatFromBytes(bytes, i);
i += 4;
snap.position.z = Tools.readFloatFromBytes(bytes, i);
// rotation
i += 4;
snap.rotation.x = Tools.readFloatFromBytes(bytes, i);
i += 4;
snap.rotation.y = Tools.readFloatFromBytes(bytes, i);
i += 4;
snap.rotation.z = Tools.readFloatFromBytes(bytes, i);
i += 4;
snap.rotation.w = Tools.readFloatFromBytes(bytes, i);
// velocity
/*i += 4;
snap.velocity.x = Tools.readFloatFromBytes(bytes, i);
i += 4;
snap.velocity.y = Tools.readFloatFromBytes(bytes, i);
i += 4;
snap.velocity.z = Tools.readFloatFromBytes(bytes, i);*/
return snap;
}
public int serialize(byte[] bytes, int offset) {
int idx = offset;
idx = Tools.writeFloatToBytes(position.x, bytes, idx);
idx = Tools.writeFloatToBytes(position.y, bytes, idx);
idx = Tools.writeFloatToBytes(position.z, bytes, idx);
idx = Tools.writeFloatToBytes(rotation.x, bytes, idx);
idx = Tools.writeFloatToBytes(rotation.y, bytes, idx);
idx = Tools.writeFloatToBytes(rotation.z, bytes, idx);
idx = Tools.writeFloatToBytes(rotation.w, bytes, idx);
/*idx = Tools.writeFloatToBytes(velocity.x, bytes, idx);
idx = Tools.writeFloatToBytes(velocity.y, bytes, idx);
idx = Tools.writeFloatToBytes(velocity.z, bytes, idx);*/
System.out.println("serialized snapshot");
for (int i = 0; i < SNAPSHOT_SIZE; i++) {
System.out.printf("[%d]: %d\n", i+offset, bytes[i+offset]);
}
return idx;
}
public void prettyPrint() {
System.out.println("--- Snapshot ---");
Tools.print(position, "position");
Tools.print(rotation, "rotation");
}
}
}