Skip to content

Commit

Permalink
domain model save state
Browse files Browse the repository at this point in the history
  • Loading branch information
Ninjabrain1 committed Sep 7, 2024
1 parent 9f1587d commit 380fc18
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 6 deletions.
6 changes: 6 additions & 0 deletions src/main/java/ninjabrainbot/gui/GUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import ninjabrainbot.io.overlay.NinjabrainBotOverlayImageWriter;
import ninjabrainbot.io.overlay.OBSOverlay;
import ninjabrainbot.io.preferences.NinjabrainBotPreferences;
import ninjabrainbot.io.savestate.TempFileAccessor;
import ninjabrainbot.io.updatechecker.GithubUpdateChecker;
import ninjabrainbot.model.ModelState;
import ninjabrainbot.model.actions.IActionExecutor;
Expand All @@ -24,6 +25,7 @@
import ninjabrainbot.model.datastate.endereye.CoordinateInputSource;
import ninjabrainbot.model.datastate.endereye.EnderEyeThrowFactory;
import ninjabrainbot.model.datastate.endereye.IEnderEyeThrowFactory;
import ninjabrainbot.model.domainmodel.DomainModelImportExportService;
import ninjabrainbot.model.domainmodel.IDomainModel;
import ninjabrainbot.model.environmentstate.IEnvironmentState;
import ninjabrainbot.model.information.CombinedCertaintyInformationProvider;
Expand Down Expand Up @@ -59,6 +61,7 @@ public class GUI {
private IActionExecutor actionExecutor;
private IEnvironmentState environmentState;
private IDataState dataState;
private DomainModelImportExportService domainModelImportExportService;

private CoordinateInputSource coordinateInputSource;
private IButtonInputHandler buttonInputHandler;
Expand Down Expand Up @@ -101,6 +104,8 @@ private void initModel() {
actionExecutor = modelState.actionExecutor;
environmentState = modelState.environmentState;
dataState = modelState.dataState;
domainModelImportExportService = new DomainModelImportExportService(domainModel, new TempFileAccessor("NinjabrainBot-save-state.txt"), preferences);
domainModelImportExportService.triggerDeserialization();
Profiler.stop();
}

Expand Down Expand Up @@ -181,6 +186,7 @@ private Thread onShutdown() {
public void run() {
preferences.windowX.set(ninjabrainBotFrame.getX());
preferences.windowY.set(ninjabrainBotFrame.getY());
domainModelImportExportService.onShutdown();
disposeHandler.dispose();
obsOverlay.dispose();
autoResetTimer.dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public class NinjabrainBotPreferences {
public final BooleanPreference colorCodeNegativeCoords;
public final BooleanPreference usePreciseAngle;
public final BooleanPreference useOverlay;
public final BooleanPreference saveState;
public final BooleanPreference enableHttpServer;
public final BooleanPreference overlayAutoHide;
public final BooleanPreference overlayHideWhenLocked;
Expand Down Expand Up @@ -124,6 +125,7 @@ public NinjabrainBotPreferences(IPreferenceSource source) {
colorCodeNegativeCoords = new BooleanPreference("color_negative_coords", false, source);
usePreciseAngle = new BooleanPreference("use_precise_angle", false, source);
useOverlay = new BooleanPreference("use_obs_overlay", false, source);
saveState = new BooleanPreference("save_state", true, source);
enableHttpServer = new BooleanPreference("enable_http_server", false, source);
overlayAutoHide = new BooleanPreference("overlay_auto_hide", false, source);
overlayHideWhenLocked = new BooleanPreference("overlay_lock_hide", false, source);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/ninjabrainbot/io/savestate/IFileAccessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ninjabrainbot.io.savestate;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public interface IFileAccessor {

ObjectOutputStream getObjectOutputStream() throws IOException;

ObjectInputStream getObjectInputStream() throws IOException;

void deleteFile();

}
33 changes: 33 additions & 0 deletions src/main/java/ninjabrainbot/io/savestate/TempFileAccessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ninjabrainbot.io.savestate;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TempFileAccessor implements IFileAccessor {

private final File file;

public TempFileAccessor(String fileName) {
file = new File(System.getProperty("java.io.tmpdir"), fileName);
}

public ObjectOutputStream getObjectOutputStream() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(file);
return new ObjectOutputStream(fileOutputStream);
}

public ObjectInputStream getObjectInputStream() throws IOException {
FileInputStream fileInputStream = new FileInputStream(file);
return new ObjectInputStream(fileInputStream);
}

@Override
public void deleteFile() {
file.delete();
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package ninjabrainbot.model.datastate.common;

import java.io.ObjectInput;
import java.io.ObjectOutput;

import ninjabrainbot.event.ISubscribable;
import ninjabrainbot.event.ObservableProperty;
import ninjabrainbot.model.domainmodel.IDomainModel;
import ninjabrainbot.model.domainmodel.IFundamentalComponent;
import ninjabrainbot.model.domainmodel.IInferredComponent;
import ninjabrainbot.model.domainmodel.SerializationException;

public class DetachedDomainModel implements IDomainModel {

Expand Down Expand Up @@ -71,4 +75,12 @@ public ISubscribable<IDomainModel> whenModified() {
public Runnable applyWriteLock(Runnable runnable) {
return runnable;
}

@Override
public void serialize(ObjectOutput objectOutput) {
}

@Override
public void deserialize(ObjectInput objectInput) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ public T getAsSerializable() {
return get();
}

@SuppressWarnings("unchecked")
@Override
public void setFromDeserializedObject(T deserialized) {
set(deserialized);
public void setFromDeserializedObject(Serializable deserialized) {
set((T) deserialized);
}

}
69 changes: 69 additions & 0 deletions src/main/java/ninjabrainbot/model/domainmodel/DomainModel.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package ninjabrainbot.model.domainmodel;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import ninjabrainbot.Main;
import ninjabrainbot.event.DisposeHandler;
import ninjabrainbot.event.IDisposable;
import ninjabrainbot.event.ISubscribable;
import ninjabrainbot.event.ObservableProperty;
import ninjabrainbot.util.Assert;
import ninjabrainbot.util.Logger;

/**
* Keeps track of all DataComponents, to manage write lock to them and monitors changes so that undo works.
Expand Down Expand Up @@ -41,6 +48,13 @@ public void finishInitialization() {
@Override
public void registerFundamentalComponent(IFundamentalComponent<?, ?> fundamentalComponent) {
Assert.isFalse(isFullyInitialized, "New IFundamentalComponents cannot be registered in the DomainModel after it has been fully initialized.");

if (fundamentalComponent.uniqueId() == null || fundamentalComponent.uniqueId().isEmpty())
throw new IllegalArgumentException("Id cannot be empty");

if (fundamentalComponents.stream().anyMatch(x -> x.uniqueId().equals(fundamentalComponent.uniqueId())))
throw new IllegalArgumentException("A component with id '" + fundamentalComponent.uniqueId() + "' has already been registered.");

fundamentalComponents.add(fundamentalComponent);
fundamentalComponent.subscribeInternal(__ -> isModifiedDuringCurrentWriteLock = true);
}
Expand Down Expand Up @@ -154,6 +168,61 @@ public void checkWriteAccess() {
throw new IllegalModificationException("Modification was attempted by thread " + Thread.currentThread().getName() + ", while the write lock is held by another thread.");
}

@Override
public void serialize(ObjectOutput objectOutput) throws SerializationException {
Assert.isTrue(isFullyInitialized);
HashMap<String, Serializable> serializedFundamentalComponents = new HashMap<>();
for (IFundamentalComponent<?, ?> fundamentalComponent : fundamentalComponents) {
String uniqueId = fundamentalComponent.uniqueId();
Serializable serializable = fundamentalComponent.getAsSerializable();
serializedFundamentalComponents.put(uniqueId, serializable);
}
serializedFundamentalComponents.put("", Main.VERSION);

try {
objectOutput.writeObject(serializedFundamentalComponents);
} catch (IOException e) {
throw new SerializationException("IOException when deserializing domain model.", e);
}
}

@SuppressWarnings("unchecked")
@Override
public void deserialize(ObjectInput objectInput) throws SerializationException {
HashMap<String, Serializable> deserializedFundamentalComponents = null;
try {
deserializedFundamentalComponents = (HashMap<String, Serializable>) objectInput.readObject();
} catch (ClassNotFoundException e) {
throw new SerializationException("Class not found when deserializing domain model.", e);
} catch (IOException e) {
throw new SerializationException("IOException when deserializing domain model.", e);
} catch (ClassCastException e) {
throw new SerializationException("ClassCastException when deserializing domain model.", e);
}

String deserializedVersion = (String) deserializedFundamentalComponents.getOrDefault("", "UNKNOWN");
if (!deserializedVersion.equals(Main.VERSION)) {
Logger.log("Domain model deserialization failed, saved data has version " + deserializedVersion + " but application has version " + Main.VERSION);
return;
}
deserializedFundamentalComponents.remove("");

if (deserializedFundamentalComponents.size() != fundamentalComponents.size())
throw new SerializationException("Expected there to be " + (fundamentalComponents.size()) + " deserialized objects, but got " + deserializedFundamentalComponents.size());

try{
acquireWriteLock();
for (IFundamentalComponent<?, ?> fundamentalComponent : fundamentalComponents) {
String uniqueId = fundamentalComponent.uniqueId();
if (!deserializedFundamentalComponents.containsKey(uniqueId))
throw new SerializationException("Key '" + uniqueId + "' is missing in list of deserialized fundamental components.");
fundamentalComponent.setFromDeserializedObject(deserializedFundamentalComponents.get(uniqueId));
}
} finally {
releaseWriteLock(true);
}
}

@Override
public void dispose() {
disposeHandler.dispose();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package ninjabrainbot.model.domainmodel;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import ninjabrainbot.io.preferences.NinjabrainBotPreferences;
import ninjabrainbot.io.savestate.IFileAccessor;
import ninjabrainbot.util.Logger;

public class DomainModelImportExportService {

private final IDomainModel domainModel;
private final IFileAccessor fileAccessor;
private final NinjabrainBotPreferences preferences;

public DomainModelImportExportService(IDomainModel domainModel, IFileAccessor fileAccessor, NinjabrainBotPreferences preferences) {
this.domainModel = domainModel;
this.fileAccessor = fileAccessor;
this.preferences = preferences;
}

public void triggerDeserialization() {
if (!preferences.saveState.get())
return;

ObjectInputStream objectInputStream = null;
try {
objectInputStream = fileAccessor.getObjectInputStream();
} catch (IOException e) {
Logger.log("Domain model deserialization failed, failed to read file: " + e);
return;
}

try {
domainModel.deserialize(objectInputStream);
try {
objectInputStream.close();
} catch (IOException ex) {
Logger.log("Failed to close ObjectInputStream after successful deserialization: " + ex);
}
} catch (SerializationException e) {
Logger.log("Domain model deserialization failed: " + e);
try {
objectInputStream.close();
fileAccessor.deleteFile();
} catch (IOException ex) {
Logger.log("Failed to close ObjectInputStream after failed deserialization: " + ex);
}
if (!domainModel.isReset())
throw new RuntimeException("Domain model is not in the default state after failed deserialization, crashing the application to avoid an inconsistent data state.");
}
}

private void triggerSerialization() {
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = fileAccessor.getObjectOutputStream();
} catch (IOException e) {
Logger.log("Domain model serialization failed, failed to open file: " + e);
return;
}

try {
domainModel.serialize(objectOutputStream);
try {
objectOutputStream.flush();
objectOutputStream.close();
} catch (IOException ex) {
Logger.log("Failed to close ObjectInputStream after successful serialization: " + ex);
}
} catch (SerializationException e) {
Logger.log("Domain model serialization failed: " + e);
try {
objectOutputStream.flush();
objectOutputStream.close();
fileAccessor.deleteFile();
} catch (IOException ex) {
Logger.log("Failed to close ObjectInputStream after failed serialization: " + ex);
}
}
}

public void onShutdown() {
triggerSerialization();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package ninjabrainbot.model.domainmodel;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

import ninjabrainbot.event.ISubscribable;

/**
Expand Down Expand Up @@ -29,4 +33,8 @@ public interface IDomainModel extends IWriteLock {

ISubscribable<IDomainModel> whenModified();

void serialize(ObjectOutput objectOutput) throws SerializationException;

void deserialize(ObjectInput objectInput) throws SerializationException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ public interface IFundamentalComponent<T, U extends Serializable> extends IDomai

U getAsSerializable();

void setFromDeserializedObject(U deserialized);
void setFromDeserializedObject(Serializable deserialized);

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ public class ListComponent<T extends Serializable> implements IListComponent<T>
public ListComponent(String uniqueId, IDomainModel domainModel, int maxCapacity) {
Assert.isNotNull(domainModel, "Domain model cannot be null");
this.domainModel = domainModel;
this.uniqueId = uniqueId;
this.maxCapacity = maxCapacity;
observableList = new ObservableList<>();
externalEvent = domainModel.createExternalEventFor(observableList);
domainModel.registerFundamentalComponent(this);
this.uniqueId = uniqueId;
}

@Override
Expand Down Expand Up @@ -150,8 +150,9 @@ public ArrayList<T> getAsSerializable() {
return arrayList;
}

@SuppressWarnings("unchecked")
@Override
public void setFromDeserializedObject(ArrayList<T> deserialized) {
set(new ArrayListImplementingReadOnlyList<>(deserialized));
public void setFromDeserializedObject(Serializable deserialized) {
set(new ArrayListImplementingReadOnlyList<>((Iterable<? extends T>) deserialized));
}
}
Loading

0 comments on commit 380fc18

Please sign in to comment.