Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate from Kryo to Json for project files #208

Merged
merged 10 commits into from
Aug 24, 2023
1 change: 1 addition & 0 deletions editor/CHANGES
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[0.6.0] ~
- [BREAKING CHANGE] Migrated from Kryo to JSON for .registry and .pro files. Migration handled automatically.
- Add dirty transform flag optimization for caching GameObject/SimpleNode world transform
- Add OrientedBoundingBox to cullable components, update debug renderer to use it
- Fix Child terrain objects not updating on translation
Expand Down
18 changes: 11 additions & 7 deletions editor/src/main/com/mbrlabs/mundus/editor/Mundus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,19 @@ import com.kotcrab.vis.ui.VisUI
import com.kotcrab.vis.ui.widget.file.FileChooser
import com.mbrlabs.mundus.commons.assets.meta.MetaLoader
import com.mbrlabs.mundus.commons.utils.DebugRenderer
import com.mbrlabs.mundus.editor.preferences.MundusPreferencesManager
import com.mbrlabs.mundus.editor.assets.MetaSaver
import com.mbrlabs.mundus.editor.assets.ModelImporter
import com.mbrlabs.mundus.editor.core.kryo.KryoManager
import com.mbrlabs.mundus.editor.core.io.IOManager
import com.mbrlabs.mundus.editor.core.io.IOManagerProvider
import com.mbrlabs.mundus.editor.core.io.MigrationIOManager
import com.mbrlabs.mundus.editor.core.project.ProjectManager
import com.mbrlabs.mundus.editor.core.registry.Registry
import com.mbrlabs.mundus.editor.events.EventBus
import com.mbrlabs.mundus.editor.history.CommandHistory
import com.mbrlabs.mundus.editor.input.FreeCamController
import com.mbrlabs.mundus.editor.input.InputManager
import com.mbrlabs.mundus.editor.input.ShortcutController
import com.mbrlabs.mundus.editor.preferences.MundusPreferencesManager
import com.mbrlabs.mundus.editor.profiling.MundusGLProfiler
import com.mbrlabs.mundus.editor.shader.Shaders
import com.mbrlabs.mundus.editor.tools.ToolManager
Expand Down Expand Up @@ -75,7 +77,7 @@ object Mundus {
private val shortcutController: ShortcutController
private val shapeRenderer: ShapeRenderer
private val debugRenderer: DebugRenderer
private val kryoManager: KryoManager
private val ioManager: IOManager
private val projectManager: ProjectManager
private val registry: Registry
private val modelImporter: ModelImporter
Expand Down Expand Up @@ -107,11 +109,11 @@ object Mundus {
input = InputManager()
goPicker = GameObjectPicker()
handlePicker = ToolHandlePicker()
kryoManager = KryoManager()
registry = kryoManager.loadRegistry()
ioManager = MigrationIOManager()
registry = ioManager.loadRegistry()
commandHistory = CommandHistory(CommandHistory.DEFAULT_LIMIT)
modelImporter = ModelImporter(registry)
projectManager = ProjectManager(kryoManager, registry, modelBatch)
projectManager = ProjectManager(ioManager, registry, modelBatch)
freeCamController = FreeCamController(projectManager, goPicker)
globalPrefManager = MundusPreferencesManager("global")
toolManager = ToolManager(input, projectManager, goPicker, handlePicker, shapeRenderer,
Expand All @@ -121,14 +123,16 @@ object Mundus {
json = Json()
glProfiler = MundusGLProfiler(Gdx.graphics)

val ioManagerProvider = IOManagerProvider(ioManager)

// add to DI container
context.register {
bindSingleton(shapeRenderer)
bindSingleton(debugRenderer)
bindSingleton(input)
bindSingleton(goPicker)
bindSingleton(handlePicker)
bindSingleton(kryoManager)
bindSingleton(ioManagerProvider)
bindSingleton(registry)
bindSingleton(commandHistory)
bindSingleton(modelImporter)
Expand Down
20 changes: 20 additions & 0 deletions editor/src/main/com/mbrlabs/mundus/editor/core/io/IOManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mbrlabs.mundus.editor.core.io;

import com.mbrlabs.mundus.editor.core.project.ProjectContext;
import com.mbrlabs.mundus.editor.core.registry.ProjectRef;
import com.mbrlabs.mundus.editor.core.registry.Registry;

import java.io.FileNotFoundException;

/**
* Manages loading and saving of registry and project data.
*
* @author JamesTKhan
* @version August 03, 2023
*/
public interface IOManager {
Registry loadRegistry();
void saveRegistry(Registry registry);
void saveProjectContext(ProjectContext context);
ProjectContext loadProjectContext(ProjectRef ref) throws FileNotFoundException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mbrlabs.mundus.editor.core.io;

/**
* Provider for IOManager. Needed because dependency injection with interfaces does
* not seem to work, so we provide this POJO to inject the IOManager.
*
* @author JamesTKhan
* @version August 03, 2023
*/
public class IOManagerProvider {
private final IOManager ioManager;

public IOManagerProvider(IOManager ioManager) {
this.ioManager = ioManager;
}

public IOManager getIOManager() {
return ioManager;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.mbrlabs.mundus.editor.core.io;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.JsonWriter;
import com.esotericsoftware.kryo.io.Input;
import com.mbrlabs.mundus.editor.core.kryo.DescriptorConverter;
import com.mbrlabs.mundus.editor.core.kryo.descriptors.ProjectDescriptor;
import com.mbrlabs.mundus.editor.core.project.ProjectContext;
import com.mbrlabs.mundus.editor.core.project.ProjectManager;
import com.mbrlabs.mundus.editor.core.registry.ProjectRef;
import com.mbrlabs.mundus.editor.core.registry.Registry;
import com.mbrlabs.mundus.editor.utils.Log;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.Writer;

/**
* Manages loading and saving of registry and project data in JSON Format.
*
* @author JamesTKhan
* @version August 03, 2023
*/
public class JsonIOManager implements IOManager {
private final Json json;

public JsonIOManager() {
json = new Json(JsonWriter.OutputType.json);
}

@Override
public Registry loadRegistry() {
FileHandle fileHandle = getHomeDataFile();

try {
return json.fromJson(Registry.class, fileHandle);
} catch (GdxRuntimeException e) {
Log.warn(getClass().getSimpleName(), "Could not load registry file. Creating new one.");
return new Registry();
}
}

@Override
public void saveRegistry(Registry registry) {
FileHandle fileHandle = getHomeDataFile();

Writer writer = fileHandle.writer(false);
json.setWriter(writer);

String jsonString = json.prettyPrint(registry);
fileHandle.writeString(jsonString, false);
}

@Override
public void saveProjectContext(ProjectContext context) {
FileHandle fileHandle = new FileHandle(context.path + "/" +
context.name + "." + ProjectManager.PROJECT_EXTENSION);

Writer writer = fileHandle.writer(false);
json.setWriter(writer);

ProjectDescriptor descriptor = DescriptorConverter.convert(context);

String jsonString = json.prettyPrint(descriptor);
fileHandle.writeString(jsonString, false);
}

@Override
public ProjectContext loadProjectContext(ProjectRef ref) throws FileNotFoundException {
// find .pro file
FileHandle projectFile = null;
for (FileHandle f : Gdx.files.absolute(ref.getPath()).list()) {
if (f.extension().equals(ProjectManager.PROJECT_EXTENSION)) {
projectFile = f;
break;
}
}

if (projectFile != null) {
Input input = new Input(new FileInputStream(projectFile.path()));
ProjectDescriptor projectDescriptor = json.fromJson(ProjectDescriptor.class, input);

ProjectContext context = DescriptorConverter.convert(projectDescriptor);
context.activeSceneName = projectDescriptor.getCurrentSceneName();
return context;
}

return null;
}

private FileHandle getHomeDataFile() {
return new FileHandle(Registry.HOME_DATA_FILE);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.mbrlabs.mundus.editor.core.io;

import com.badlogic.gdx.Gdx;
import com.esotericsoftware.kryo.KryoException;
import com.mbrlabs.mundus.editor.Mundus;
import com.mbrlabs.mundus.editor.core.kryo.KryoManager;
import com.mbrlabs.mundus.editor.core.project.ProjectContext;
import com.mbrlabs.mundus.editor.core.registry.ProjectRef;
import com.mbrlabs.mundus.editor.core.registry.Registry;
import com.mbrlabs.mundus.editor.events.LogEvent;

import java.io.FileNotFoundException;

/**
* An intermediate class that handles the migration of project data from Kryo to Json formatting
* if necessary. If the project data is already in Json format, then this class will simply call JsonIOManager.
* Eventually this class will be removed in favor of JsonIOManager.
*
* Backs up kryo .registry and .pro files before replacing them with JSON versions.
*
* @author JamesTKhan
* @version August 03, 2023
*/
public class MigrationIOManager extends JsonIOManager {
private KryoManager kryoManager;

public MigrationIOManager() {
// Kryo with TaggedFieldSerializer is not compatible with Java 9+ out of box
// due to reflection. If JDK <= 1.8 we will check if the registry is still kryo and
// if so, migrate it over to Json. If JDK > 1.8 we will just use JsonIOManager
String version = System.getProperty("java.specification.version");
double versionDouble = Double.parseDouble(version);
if (versionDouble <= 1.8) {
kryoManager = new KryoManager();
}
}
@Override
public Registry loadRegistry() {
if (kryoManager != null && kryoManager.isRegistryKryo()) {
migrateRegistry();
}

return super.loadRegistry();
}

@Override
public ProjectContext loadProjectContext(ProjectRef ref) throws FileNotFoundException {
if (kryoManager != null) {
try {
ProjectContext projectContext = kryoManager.loadProjectContext(ref);
migrateProject(ref, projectContext);
} catch (KryoException e) {
// Assume that the project is in Json format already
}
}

return super.loadProjectContext(ref);
}

private void migrateRegistry() {
kryoManager.backupRegistry();

// Migrate registry to Json
Registry kryoRegistry = kryoManager.loadRegistry();
saveRegistry(kryoRegistry);
}

private void migrateProject(ProjectRef project, ProjectContext kryoProject) {
// Migrate project to Json
if (kryoProject == null) return;

String log = "Migrating project " + project.getName() + " to Json format";
Mundus.INSTANCE.postEvent(new LogEvent(log));
Gdx.app.log("MigrationIOManager", log);

kryoProject.path = project.getPath();
kryoProject.currScene.setName(kryoProject.activeSceneName);

kryoManager.backupProjectContext(kryoProject);
saveProjectContext(kryoProject);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.TaggedFieldSerializer;
import com.mbrlabs.mundus.editor.core.io.IOManager;
import com.mbrlabs.mundus.editor.core.kryo.descriptors.*;
import com.mbrlabs.mundus.editor.core.project.ProjectContext;
import com.mbrlabs.mundus.editor.core.project.ProjectManager;
Expand All @@ -45,7 +47,7 @@
* @author Marcus Brummer
* @version 12-12-2015
*/
public class KryoManager {
public class KryoManager implements IOManager {

private Kryo kryo;

Expand All @@ -68,7 +70,6 @@ public KryoManager() {
kryo.register(KeyboardLayout.class, 13);
kryo.register(ProjectDescriptor.class, 14);
kryo.register(SceneRefDescriptor.class, 15);

}

/**
Expand Down Expand Up @@ -166,6 +167,49 @@ public ProjectContext loadProjectContext(ProjectRef ref) throws FileNotFoundExce
return null;
}

/**
* Checks if the registry is saved with kryo serializer to aid in migration to Json format
*/
public boolean isRegistryKryo() {
Input input = null;
try {
input = new Input(new FileInputStream(Registry.HOME_DATA_FILE));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}

try {
kryo.readObjectOrNull(input, RegistryDescriptor.class);
} catch (KryoException e) {
// Assume it's not kryo if we get an exception
return false;
}

return true;
}

/**
* Creates a backup of the registry.
*/
public void backupRegistry() {
FileHandle file = Gdx.files.absolute(Registry.HOME_DATA_FILE);
FileHandle backup = Gdx.files.absolute(Registry.HOME_DATA_FILE + ".kryo.bak");
file.copyTo(backup);
Gdx.app.log("Mundus", "Created Kryo registry backup: " + backup.path());
}

/**
* Creates a backup of the project context.
*/
public void backupProjectContext(ProjectContext context) {
FileHandle file = Gdx.files.absolute(context.path + "/" +
context.name + "." + ProjectManager.PROJECT_EXTENSION);
FileHandle backup = Gdx.files.absolute(context.path + "/" +
context.name + "." + ProjectManager.PROJECT_EXTENSION + ".kryo.bak");
file.copyTo(backup);
Gdx.app.log("Mundus", "Created Kryo project context backup: " + backup.path());
}

// /**
// * Saves a scene.
// *
Expand Down
Loading