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

feat: Add restoration checkpoint #108

Merged
merged 4 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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