Skip to content

Commit

Permalink
Merge pull request #108 from suzuryg/feat/restoration-checkpoint
Browse files Browse the repository at this point in the history
feat: Add restoration checkpoint
  • Loading branch information
suzuryg authored Jan 20, 2024
2 parents d0d1cb2 + c171581 commit df135f6
Show file tree
Hide file tree
Showing 14 changed files with 294 additions and 49 deletions.
15 changes: 11 additions & 4 deletions Packages/jp.suzuryg.face-emo/Editor/AppMain/FaceEmoInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@ public FaceEmoInstaller(GameObject launcherObject)
RootObjectName = launcherObject.name;

// Bind Monobehaviour instances
var menuRepositoryComponent = launcherObject.GetComponent<MenuRepositoryComponent>();
if (menuRepositoryComponent == null) { menuRepositoryComponent = launcherObject.AddComponent<MenuRepositoryComponent>(); }
menuRepositoryComponent.hideFlags = HideFlags.HideInInspector;
Container.Bind<MenuRepositoryComponent>().FromInstance(menuRepositoryComponent).AsSingle();
BindComponent<MenuRepositoryComponent>(launcherObject);
BindComponent<RestorationCheckpoint>(launcherObject);

var launcher = launcherObject.GetComponent<FaceEmoLauncherComponent>();
if (launcher == null) { launcher = launcherObject.AddComponent<FaceEmoLauncherComponent>(); }
Expand Down Expand Up @@ -158,6 +156,7 @@ public FaceEmoInstaller(GameObject launcherObject)
Container.Bind<DefaultsProviderGenerator>().AsTransient();

Container.Bind<IFxGenerator>().To<FxGenerator>().AsTransient();
Container.Bind<IRestorer>().To<FaceEmoRestorer>().AsTransient();

Container.Bind<MainView>().AsTransient();
Container.Bind<HierarchyView>().AsTransient();
Expand Down Expand Up @@ -210,5 +209,13 @@ public static FaceEmoInstaller GetInstaller(string rootObjectPath)

return new FaceEmoInstaller(launcherObject);
}

private void BindComponent<T>(GameObject gameObject) where T : MonoBehaviour
{
var component = gameObject.GetComponent<T>();
if (component == null) { component = gameObject.AddComponent<T>(); }
component.hideFlags = HideFlags.HideInInspector;
Container.Bind<T>().FromInstance(component).AsSingle();
}
}
}
146 changes: 108 additions & 38 deletions Packages/jp.suzuryg.face-emo/Editor/AppMain/FaceEmoLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ private void ChangeLocale(Locale locale)
}

[MenuItem("FaceEmo/New Menu", false, 0)]
public static GameObject Create(MenuCommand menuCommand)
public static GameObject CreateCommand()
{
var gameObject = GetLauncherObject(menuCommand);
var gameObject = GetLauncherObject();

// Create GameObject which has unique name in acitive scene.
var baseName = DomainConstants.SystemName;
Expand All @@ -148,38 +148,77 @@ public static GameObject Create(MenuCommand menuCommand)
}

[MenuItem("FaceEmo/Restore Menu", false, 1)]
public static void Restore(MenuCommand menuCommand)
public static void RestoreCommand()
{
var selectedPath = EditorUtility.OpenFilePanelWithFilters(title: null, directory: FaceEmoBackupper.BackupDir, filters: new[] { "FaceEmoProject" , "asset" });
if (string.IsNullOrEmpty(selectedPath)) { return; }

// OpenFilePanel path is in OS format, so convert it to Unity format
var unityPath = PathConverter.ToUnityPath(selectedPath);

var gameObject = GetLauncherObject(menuCommand);
var name = System.IO.Path.GetFileName(selectedPath).Replace(".asset", string.Empty);
gameObject.name = name;

var installer = new FaceEmoInstaller(gameObject);
var backupper = installer.Container.Resolve<IBackupper>();
try
{
backupper.Import(unityPath);
Restore(unityPath);
}
catch (Exception ex)
{
var loc = LocalizationSetting.GetTable(LocalizationSetting.GetLocale());
EditorUtility.DisplayDialog(DomainConstants.SystemName, loc.Common_Message_FailedToOpenProject + "\n" + loc.Common_Message_SeeConsole, "OK");
Debug.LogError(loc.Common_Message_FailedToOpenProject + "\n" + ex?.ToString());
}
}

public static bool CanRestore(RestorationCheckpoint restorationCheckpoint)
{
return
restorationCheckpoint != null &&
restorationCheckpoint.LatestBackup != null &&
!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(restorationCheckpoint.LatestBackup));
}

public static GameObject Restore(RestorationCheckpoint restorationCheckpoint)
{
var launcherObject = Restore(AssetDatabase.GetAssetPath(restorationCheckpoint.LatestBackup));

restorationCheckpoint.gameObject.SetActive(false);
Selection.activeGameObject = launcherObject;

// Close MainWindow to avoid users editing the old menu.
var mainWindow = EditorWindow.GetWindow<MainWindow>();
if (mainWindow != null) { mainWindow.Close(); }

var newCheckpoint = launcherObject.GetComponent<RestorationCheckpoint>();
if (newCheckpoint == null) { launcherObject.AddComponent<RestorationCheckpoint>(); }
EditorUtility.CopySerialized(restorationCheckpoint, newCheckpoint);

return launcherObject;
}

private static GameObject Restore(string path)
{
var gameObject = GetLauncherObject();
var name = System.IO.Path.GetFileName(path).Replace(".asset", string.Empty);
gameObject.name = name;

var installer = new FaceEmoInstaller(gameObject);
var backupper = installer.Container.Resolve<IBackupper>();
try
{
backupper.Import(path);
return gameObject;
}
catch (Exception)
{
if (gameObject != null) { DestroyImmediate(gameObject); }
throw;
}
}

private static GameObject GetLauncherObject(MenuCommand menuCommand)
private static GameObject GetLauncherObject(MenuCommand menuCommand = null)
{
var gameObject = new GameObject();
gameObject.AddComponent<FaceEmoLauncherComponent>();
GameObjectUtility.SetParentAndAlign(gameObject, menuCommand.context as GameObject);
if (menuCommand != null) { GameObjectUtility.SetParentAndAlign(gameObject, menuCommand.context as GameObject); }
Undo.RegisterCreatedObjectUndo(gameObject, $"Create {DomainConstants.SystemName} Object");
Selection.activeObject = gameObject;
UnityEditorInternal.InternalEditorUtility.SetIsInspectorExpanded(gameObject.GetComponent<FaceEmoLauncherComponent>(), true);
Expand Down Expand Up @@ -224,46 +263,77 @@ private static void HierarchyWindowItemOnGUI(int instanceID, Rect selectionRect)
{
if (!CanLaunch()) { return; }

var exists = false;
foreach (var launcher in FindObjectsOfType<FaceEmoLauncherComponent>()?.OrderBy(x => x.name))
{
if (ReferenceEquals(launcher?.AV3Setting?.TargetAvatar, avatarDescriptor))
{
Launch(launcher);
exists = true;
break;
}
}
if (TryLaunchFromExisting(avatarDescriptor)) { return; }

if (TryLaunchFromCheckpoint(avatarDescriptor, loc)) { return; }

LaunchFromNew(avatarDescriptor, loc);
}
GUI.DrawTexture(selectionRect, icon, ScaleMode.ScaleToFit, alphaBlend: true);
}

if (!exists)
private static bool TryLaunchFromExisting(VRCAvatarDescriptor avatarDescriptor)
{
foreach (var launcher in FindObjectsOfType<FaceEmoLauncherComponent>()?.OrderBy(x => x.name))
{
if (ReferenceEquals(launcher?.AV3Setting?.TargetAvatar, avatarDescriptor))
{
var launcherObject = Create(new MenuCommand(null, 0));
var installer = new FaceEmoInstaller(launcherObject);
Launch(launcher);
return true;
}
}
return false;
}

var launcher = launcherObject.GetComponent<FaceEmoLauncherComponent>();
launcher.AV3Setting.TargetAvatar = avatarDescriptor;
private static bool TryLaunchFromCheckpoint(VRCAvatarDescriptor avatarDescriptor, LocalizationTable loc)
{
foreach (var checkpoint in FindObjectsOfType<RestorationCheckpoint>()?.OrderBy(x => x.name))
{
if (!ReferenceEquals(checkpoint.TargetAvatar, avatarDescriptor) || !CanRestore(checkpoint)) { continue; }

if (OptoutableDialog.Show(DomainConstants.SystemName, loc.Launcher_Message_Restore, "OK", isRiskyAction: false))
{
try
{
ImportPatternsAndOptions(launcherObject, installer, avatarDescriptor);
var launcherObject = Restore(checkpoint);
var launcher = launcherObject.GetComponent<FaceEmoLauncherComponent>();
Launch(launcher);
return true;
}
catch (Exception ex)
{
EditorUtility.DisplayDialog(DomainConstants.SystemName, loc.Launcher_Message_ImportError + "\n\n" + ex?.Message, "OK");
Debug.LogError(loc.Launcher_Message_ImportError + ex?.ToString());
EditorUtility.DisplayDialog(DomainConstants.SystemName, loc.Launcher_Message_RestoreError + "\n\n" + ex?.Message, "OK");
Debug.LogError(loc.Launcher_Message_RestoreError + ex?.ToString());
return false;
}
}
}
return false;
}

UnityEngine.Object.DestroyImmediate(launcher);
private static void LaunchFromNew(VRCAvatarDescriptor avatarDescriptor, LocalizationTable loc)
{
var launcherObject = CreateCommand();
var installer = new FaceEmoInstaller(launcherObject);

installer = new FaceEmoInstaller(launcherObject);
var launcher = launcherObject.GetComponent<FaceEmoLauncherComponent>();
launcher.AV3Setting.TargetAvatar = avatarDescriptor;

launcher = launcherObject.GetComponent<FaceEmoLauncherComponent>();
launcher.AV3Setting.TargetAvatar = avatarDescriptor;
}
try
{
ImportPatternsAndOptions(launcherObject, installer, avatarDescriptor);
}
catch (Exception ex)
{
EditorUtility.DisplayDialog(DomainConstants.SystemName, loc.Launcher_Message_ImportError + "\n\n" + ex?.Message, "OK");
Debug.LogError(loc.Launcher_Message_ImportError + ex?.ToString());

Launch(launcher);
}
DestroyImmediate(launcher);
new FaceEmoInstaller(launcherObject);
launcher = launcherObject.GetComponent<FaceEmoLauncherComponent>();
launcher.AV3Setting.TargetAvatar = avatarDescriptor;
}
GUI.DrawTexture(selectionRect, icon, ScaleMode.ScaleToFit, alphaBlend: true);
Launch(launcher);
}

private static void ImportPatternsAndOptions(GameObject rootObject, FaceEmoInstaller installer, VRCAvatarDescriptor avatarDescriptor)
Expand Down
26 changes: 26 additions & 0 deletions Packages/jp.suzuryg.face-emo/Editor/AppMain/FaceEmoRestorer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Suzuryg.FaceEmo.AppMain;
using Suzuryg.FaceEmo.Components;

namespace Suzuryg.FaceEmo.Detail
{
public class FaceEmoRestorer : IRestorer
{
private RestorationCheckpoint _restorationCheckpoint;

public FaceEmoRestorer(RestorationCheckpoint restorationCheckpoint)
{
_restorationCheckpoint = restorationCheckpoint;
}

public bool CanRestore() => FaceEmoLauncher.CanRestore(_restorationCheckpoint);
public void Restore() => FaceEmoLauncher.Restore(_restorationCheckpoint);

public (string current, string backup) GetNames()
{
var current = _restorationCheckpoint.gameObject != null ? _restorationCheckpoint.gameObject.name : string.Empty;
var backup = _restorationCheckpoint.LatestBackup != null ? _restorationCheckpoint.LatestBackup.name : string.Empty;
return (current, backup);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,7 @@ private AacFlClip GetMouthMorphCancelerAnimation(AV3Setting aV3Setting, AacFlBas

#pragma warning disable CS0612
var obsolete = aV3Setting.MouthMorphBlendShapes;
#pragma warning restore CS0612
if (obsolete.Any())
{
var faceMesh = AV3Utility.GetFaceMesh(avatarDescriptor);
Expand All @@ -1553,7 +1554,6 @@ private AacFlClip GetMouthMorphCancelerAnimation(AV3Setting aV3Setting, AacFlBas
EditorUtility.SetDirty(aV3Setting);
}
}
#pragma warning restore CS0612

var excludeBlink = false;
var excludeLipSync = true;
Expand Down
32 changes: 28 additions & 4 deletions Packages/jp.suzuryg.face-emo/Editor/Detail/FaceEmoBackupper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Suzuryg.FaceEmo.Components.Settings;
using Suzuryg.FaceEmo.Detail.AV3;
using Suzuryg.FaceEmo.Detail.Data;
using Suzuryg.FaceEmo.Detail.Drawing;
using Suzuryg.FaceEmo.Detail.View;
using UnityEditor;
using UnityEngine;
using Suzuryg.FaceEmo.Domain;
Expand All @@ -25,24 +25,33 @@ public class FaceEmoBackupper : IBackupper, IDisposable

private string _backupName;
private IMenuBackupper _menuBackupper;
private IMenuRepository _menuRepository;
private SelectionSynchronizer _selectionSynchronizer;
private AV3Setting _aV3Setting;
private ExpressionEditorSetting _expressionEditorSetting;
private ThumbnailSetting _thumbnailSetting;
private SerializedObject _restorationCheckpoint;
private LocalizationTable _localizationTable;

private CompositeDisposable _disposables = new CompositeDisposable();

public FaceEmoBackupper(
IMenuBackupper menuBackupper,
IMenuRepository menuRepository,
SelectionSynchronizer selectionSynchronizer,
AV3Setting aV3Setting,
ExpressionEditorSetting expressionEditorSetting,
ThumbnailSetting thumbnailSetting,
RestorationCheckpoint restorationCheckpoint,
IReadOnlyLocalizationSetting localizationSetting)
{
_menuBackupper = menuBackupper;
_menuRepository = menuRepository;
_selectionSynchronizer = selectionSynchronizer;
_aV3Setting = aV3Setting;
_expressionEditorSetting = expressionEditorSetting;
_thumbnailSetting = thumbnailSetting;
_restorationCheckpoint = new SerializedObject(restorationCheckpoint);

// Localization table changed event handler
localizationSetting.OnTableChanged.Synchronize().Subscribe(SetText).AddTo(_disposables);
Expand Down Expand Up @@ -73,10 +82,17 @@ public void AutoBackup()
}

var path = $"{dir}/{_backupName}_{DateTime.Now.ToString("yyyyMMdd_HHmmss")}.asset";
Export(path);
var project = SaveProject(path);

_restorationCheckpoint.Update();
_restorationCheckpoint.FindProperty(nameof(RestorationCheckpoint.TargetAvatar)).objectReferenceValue = _aV3Setting.TargetAvatar;
_restorationCheckpoint.FindProperty(nameof(RestorationCheckpoint.LatestBackup)).objectReferenceValue = project;
_restorationCheckpoint.ApplyModifiedProperties();
}

public void Export(string path)

public void Export(string path) => SaveProject(path);

public FaceEmoProject SaveProject(string path)
{
BackupPath();

Expand Down Expand Up @@ -106,6 +122,8 @@ public void Export(string path)
project.ThumbnailSetting.name = nameof(ThumbnailSetting);

AssetDatabase.SaveAssets();

return project;
}

public void Import(string path)
Expand All @@ -122,6 +140,12 @@ public void Import(string path)
EditorUtility.CopySerialized(imported.ThumbnailSetting, _thumbnailSetting);

RestorePath();

var menu = _menuRepository.Load(string.Empty);
if (menu.Registered.Order.Any())
{
_selectionSynchronizer.ChangeMenuItemListViewSelection(menu.Registered.Order.First());
}
}

private void SetText(LocalizationTable localizationTable)
Expand Down
10 changes: 10 additions & 0 deletions Packages/jp.suzuryg.face-emo/Editor/Detail/IRestorer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Suzuryg.FaceEmo.Detail
{
public interface IRestorer
{
bool CanRestore();
void Restore();
(string current, string backup) GetNames();
}
}

Loading

0 comments on commit df135f6

Please sign in to comment.