1
0

存档支持云同步

This commit is contained in:
ALIENJACK\alien 2025-04-18 13:38:45 +08:00
parent 13cddd8a3b
commit eb2f5d268c
15 changed files with 236 additions and 112 deletions

View File

@ -1,6 +1,7 @@
using AxibugEmuOnline.Client.ClientCore;
using AxibugEmuOnline.Client.Tools;
using AxibugProtobuf;
using MAME.Core;
using System;
using System.IO;
using System.Runtime.InteropServices;
@ -8,7 +9,7 @@ using System.Runtime.InteropServices;
namespace AxibugEmuOnline.Client
{
/// <summary> 存档文件抽象类 </summary>
public class SaveFile
public partial class SaveFile
{
public SavCloudApi CloudAPI => App.SavMgr.CloudApi;
@ -38,7 +39,24 @@ namespace AxibugEmuOnline.Client
}
}
public bool IsBusy
{
get
{
if (FSM.CurrentState is DownloadingState) return true;
else if (FSM.CurrentState is UploadingState) return true;
return false;
}
}
public SimpleFSM<SaveFile>.State GetState()
{
return FSM.CurrentState;
}
public event Action OnSavSuccessed;
public event Action OnStateChanged;
/// <summary> 存档顺序号,用于判断云存档和本地存档的同步状态,是否存在冲突 </summary>
public uint Sequecen { get; private set; }
@ -55,11 +73,12 @@ namespace AxibugEmuOnline.Client
EmuPlatform = platform;
SlotIndex = slotIndex;
FSM = new SimpleFSM<SaveFile>(this);
FSM.AddState<UnkownState>();
FSM.AddState<IdleState>();
FSM.AddState<CheckingState>();
FSM.AddState<DownloadingState>();
FSM.AddState<UploadingState>();
FSM.AddState<SyncedState>();
FSM.OnStateChanged += FSM_OnStateChanged;
IsEmpty = !File.Exists(FilePath);
@ -82,7 +101,12 @@ namespace AxibugEmuOnline.Client
streaming.Dispose();
}
FSM.ChangeState<UnkownState>();
FSM.ChangeState<IdleState>();
}
private void FSM_OnStateChanged()
{
OnStateChanged?.Invoke();
}
public void Update()
@ -145,6 +169,8 @@ namespace AxibugEmuOnline.Client
public unsafe void Save(uint sequence, byte[] savData, byte[] screenShotData)
{
if (IsBusy) return;
var filePath = FilePath;
var header = new Header
@ -190,9 +216,9 @@ namespace AxibugEmuOnline.Client
/// </summary>
public void TrySync()
{
if (FSM.CurrentState is not UnkownState && FSM.CurrentState is not SyncedState) return;
if (FSM.CurrentState is not IdleState && FSM.CurrentState is not SyncedState) return;
FSM.ChangeState<CheckingState>();
FSM.ChangeState<CheckingNetworkState>();
}
@ -210,14 +236,5 @@ namespace AxibugEmuOnline.Client
[FieldOffset(20)]
public uint ScreenShotLength;
}
public enum EnumState
{
Unkown,
Checking,
Downloading,
Uploading,
Synced
}
}
}

View File

@ -0,0 +1,21 @@
using AxibugEmuOnline.Client.ClientCore;
using AxibugEmuOnline.Client.Tools;
using System;
namespace AxibugEmuOnline.Client
{
public partial class SaveFile
{
public class CheckingNetworkState : SimpleFSM<SaveFile>.State
{
public override void OnUpdate()
{
if (App.network.isConnected)
{
FSM.ChangeState<CheckingState>();
}
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 288e73ce3cb773e46b7d37a53a3b43ee

View File

@ -4,45 +4,48 @@ using UnityEngine;
namespace AxibugEmuOnline.Client
{
public class CheckingState : SimpleFSM<SaveFile>.State
public partial class SaveFile
{
private float m_timeOut;
public Protobuf_Mine_GameSavInfo NetData { get; private set; }
public override void OnEnter(SimpleFSM<SaveFile>.State preState)
public class CheckingState : SimpleFSM<SaveFile>.State
{
m_timeOut = 5f;
Host.CloudAPI.OnFetchGameSavList += CloudAPI_OnFetchGameSavList;
Host.CloudAPI.SendGetGameSavList(Host.RomID);
}
private float m_timeOut;
public override void OnExit(SimpleFSM<SaveFile>.State nextState)
{
Host.CloudAPI.OnFetchGameSavList -= CloudAPI_OnFetchGameSavList;
}
public Protobuf_Mine_GameSavInfo NetData { get; private set; }
public override void OnUpdate()
{
m_timeOut -= Time.deltaTime;
if (m_timeOut < 0) //已超时
public override void OnEnter(SimpleFSM<SaveFile>.State preState)
{
FSM.ChangeState<UnkownState>();
m_timeOut = 5f;
Host.CloudAPI.OnFetchGameSavList += CloudAPI_OnFetchGameSavList;
Host.CloudAPI.SendGetGameSavList(Host.RomID);
}
}
private void CloudAPI_OnFetchGameSavList(int romID, Protobuf_Mine_GameSavInfo[] savSlotData)
{
if (romID != Host.RomID) return;
NetData = savSlotData[Host.SlotIndex];
if (NetData == null) //云存档不存在,上传本地存档
public override void OnExit(SimpleFSM<SaveFile>.State nextState)
{
FSM.ChangeState<UploadingState>();
Host.CloudAPI.OnFetchGameSavList -= CloudAPI_OnFetchGameSavList;
}
else
public override void OnUpdate()
{
FSM.ChangeState<DownloadingState>();
m_timeOut -= Time.deltaTime;
if (m_timeOut < 0) //已超时
{
FSM.ChangeState<IdleState>();
}
}
private void CloudAPI_OnFetchGameSavList(int romID, Protobuf_Mine_GameSavInfo[] savSlotData)
{
if (romID != Host.RomID) return;
NetData = savSlotData[Host.SlotIndex];
if (NetData == null) //云存档不存在,上传本地存档
{
FSM.ChangeState<UploadingState>();
}
else
{
FSM.ChangeState<DownloadingState>();
}
}
}
}

View File

@ -2,8 +2,11 @@
namespace AxibugEmuOnline.Client
{
public class ConflictState : SimpleFSM<SaveFile>.State
public partial class SaveFile
{
public class ConflictState : SimpleFSM<SaveFile>.State
{
}
}
}

View File

@ -3,51 +3,54 @@ using AxibugEmuOnline.Client.Tools;
namespace AxibugEmuOnline.Client
{
public class DownloadingState : SimpleFSM<SaveFile>.State
public partial class SaveFile
{
uint m_sequece;
private AxiHttpProxy.SendDownLoadProxy m_downloadTask;
private AxiHttpProxy.SendDownLoadProxy m_downloadTaskImg;
public override void OnEnter(SimpleFSM<SaveFile>.State preState)
public class DownloadingState : SimpleFSM<SaveFile>.State
{
var checkState = preState as CheckingState;
uint m_sequece;
private AxiHttpProxy.SendDownLoadProxy m_downloadTask;
private AxiHttpProxy.SendDownLoadProxy m_downloadTaskImg;
var netData = checkState.NetData;
if (Host.Sequecen >= (uint)netData.Sequence)
public override void OnEnter(SimpleFSM<SaveFile>.State preState)
{
FSM.ChangeState<ConflictState>();
return;
var checkState = preState as CheckingState;
var netData = checkState.NetData;
if (Host.Sequecen >= (uint)netData.Sequence)
{
FSM.ChangeState<ConflictState>();
return;
}
m_sequece = (uint)netData.Sequence;
m_downloadTask = AxiHttpProxy.GetDownLoad($"{App.httpAPI.WebHost}/{netData.SavUrl}");
m_downloadTaskImg = AxiHttpProxy.GetDownLoad($"{App.httpAPI.WebHost}/{netData.SavImgUrl}");
}
m_sequece = (uint)netData.Sequence;
m_downloadTask = AxiHttpProxy.GetDownLoad($"{App.httpAPI.WebHost}/{netData.SavUrl}");
m_downloadTaskImg = AxiHttpProxy.GetDownLoad($"{App.httpAPI.WebHost}/{netData.SavImgUrl}");
}
public override void OnUpdate()
{
if (!m_downloadTask.downloadHandler.isDone) return;
if (m_downloadTask.downloadHandler.bHadErr) //下载失败
public override void OnUpdate()
{
FSM.ChangeState<UnkownState>();
return;
if (!m_downloadTask.downloadHandler.isDone) return;
if (m_downloadTask.downloadHandler.bHadErr) //下载失败
{
FSM.ChangeState<IdleState>();
return;
}
if (!m_downloadTaskImg.downloadHandler.isDone) return;
if (m_downloadTaskImg.downloadHandler.bHadErr) //下载失败
{
FSM.ChangeState<IdleState>();
return;
}
var savData = Host.CloudAPI.UnGzipData(m_downloadTask.downloadHandler.data);
var imgData = Host.CloudAPI.UnGzipData(m_downloadTaskImg.downloadHandler.data);
Host.Save(m_sequece, savData, imgData);
FSM.ChangeState<SyncedState>();
}
if (!m_downloadTaskImg.downloadHandler.isDone) return;
if (m_downloadTaskImg.downloadHandler.bHadErr) //下载失败
{
FSM.ChangeState<UnkownState>();
return;
}
var savData = Host.CloudAPI.UnGzipData(m_downloadTask.downloadHandler.data);
var imgData = Host.CloudAPI.UnGzipData(m_downloadTaskImg.downloadHandler.data);
Host.Save(m_sequece, savData, imgData);
FSM.ChangeState<SyncedState>();
}
}
}

View File

@ -0,0 +1,11 @@
using AxibugEmuOnline.Client.Tools;
namespace AxibugEmuOnline.Client
{
public partial class SaveFile
{
public class IdleState : SimpleFSM<SaveFile>.State
{
}
}
}

View File

@ -2,7 +2,10 @@
namespace AxibugEmuOnline.Client
{
public class SyncedState : SimpleFSM<SaveFile>.State
public partial class SaveFile
{
public class SyncedState : SimpleFSM<SaveFile>.State
{
}
}
}

View File

@ -1,8 +0,0 @@
using AxibugEmuOnline.Client.Tools;
namespace AxibugEmuOnline.Client
{
public class UnkownState : SimpleFSM<SaveFile>.State
{
}
}

View File

@ -2,27 +2,30 @@
namespace AxibugEmuOnline.Client
{
public class UploadingState : SimpleFSM<SaveFile>.State
public partial class SaveFile
{
public override void OnEnter(SimpleFSM<SaveFile>.State preState)
public class UploadingState : SimpleFSM<SaveFile>.State
{
Host.CloudAPI.OnUploadedSavData += Api_OnUploadedSavData;
public override void OnEnter(SimpleFSM<SaveFile>.State preState)
{
Host.CloudAPI.OnUploadedSavData += Api_OnUploadedSavData;
Host.GetSavData(out byte[] savData, out byte[] screenData);
Host.CloudAPI.SendUpLoadGameSav(Host.RomID, Host.SlotIndex, Host.Sequecen, savData, screenData);
}
Host.GetSavData(out byte[] savData, out byte[] screenData);
Host.CloudAPI.SendUpLoadGameSav(Host.RomID, Host.SlotIndex, Host.Sequecen, savData, screenData);
}
public override void OnExit(SimpleFSM<SaveFile>.State nextState)
{
Host.CloudAPI.OnUploadedSavData -= Api_OnUploadedSavData;
}
public override void OnExit(SimpleFSM<SaveFile>.State nextState)
{
Host.CloudAPI.OnUploadedSavData -= Api_OnUploadedSavData;
}
private void Api_OnUploadedSavData(int romID, int slotIndex, AxibugProtobuf.Protobuf_Mine_GameSavInfo savInfo)
{
if (Host.RomID != romID) return;
if (Host.SlotIndex != slotIndex) return;
private void Api_OnUploadedSavData(int romID, int slotIndex, AxibugProtobuf.Protobuf_Mine_GameSavInfo savInfo)
{
if (Host.RomID != romID) return;
if (Host.SlotIndex != slotIndex) return;
FSM.ChangeState<SyncedState>();
FSM.ChangeState<SyncedState>();
}
}
}
}

View File

@ -4,10 +4,10 @@ using System.Linq;
namespace AxibugEmuOnline.Client.Tools
{
public class SimpleFSM<HOST>
public partial class SimpleFSM<HOST>
{
public event Action OnStateChanged;
private Dictionary<Type, State> m_states = new Dictionary<Type, State>();
private State m_current;
public HOST Host { get; private set; }
@ -16,7 +16,18 @@ namespace AxibugEmuOnline.Client.Tools
Host = host;
}
public State CurrentState => m_current;
private State m_current;
public State CurrentState
{
get => m_current;
set
{
if (m_current == value) return;
m_current = value;
OnStateChanged?.Invoke();
}
}
public T AddState<T>() where T : State, new()
{
var stateType = typeof(T);

View File

@ -46,6 +46,13 @@ namespace AxibugEmuOnline.Client
m_subOptions.Add(new LoadMenuItem(inGameui, savFile));
}
public override void OnShow(OptionUI_MenuItem ui)
{
base.OnShow(ui);
SavFile.TrySync();
}
protected override List<InternalOptionMenu> GetOptionMenus()
{
return m_subOptions;
@ -65,6 +72,13 @@ namespace AxibugEmuOnline.Client
public override void OnExcute(OptionUI optionUI, ref bool cancelHide)
{
if (m_savFile.IsBusy)
{
OverlayManager.PopTip("存档正在同步中");
cancelHide = true;
return;
}
var stateData = m_ingameUI.Core.GetStateBytes();
var tex = m_ingameUI.Core.OutputPixel;
var screenData = tex.ToJPG(m_ingameUI.Core.DrawCanvas.transform.localScale);

View File

@ -439,7 +439,6 @@ namespace AxibugEmuOnline.Client
public abstract string Name { get; }
public virtual Sprite Icon { get; }
public virtual bool Visible => true;
public virtual bool Enable => true;
public virtual void OnFocus() { }
public virtual void OnShow(OptionUI_MenuItem ui) { }

View File

@ -1,4 +1,7 @@
using UnityEngine;
using AxibugEmuOnline.Client.Tools;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using static AxibugEmuOnline.Client.InGameUI_SaveStateMenu;
@ -10,15 +13,38 @@ namespace AxibugEmuOnline.Client
public Image UI_Empty;
public Text UI_SavTime;
public GameObject UI_Disconnect;
public GameObject UI_Syncing;
public GameObject UI_Conflict;
public GameObject UI_Synced;
Texture2D m_screenTex;
Dictionary<Type, GameObject> m_stateNodes = new Dictionary<Type, GameObject>();
private void Awake()
{
m_stateNodes[typeof(SaveFile.CheckingState)] = UI_Syncing;
m_stateNodes[typeof(SaveFile.ConflictState)] = UI_Conflict;
m_stateNodes[typeof(SaveFile.DownloadingState)] = UI_Syncing;
m_stateNodes[typeof(SaveFile.SyncedState)] = UI_Synced;
m_stateNodes[typeof(SaveFile.UploadingState)] = UI_Syncing;
m_stateNodes[typeof(SaveFile.CheckingNetworkState)] = UI_Disconnect;
}
protected override void OnSetData(InternalOptionMenu menuData)
{
base.OnSetData(menuData);
RefreshUI();
MenuData.SavFile.OnSavSuccessed += SavFile_OnSavSuccessed;
MenuData.SavFile.OnStateChanged += UpdateStateNode;
}
MenuData.SavFile.OnSavSuccessed += RefreshUI;
private void SavFile_OnSavSuccessed()
{
MenuData.SavFile.TrySync();
RefreshUI();
}
private void RefreshUI()
@ -48,6 +74,21 @@ namespace AxibugEmuOnline.Client
m_screenTex.LoadImage(screenShotData);
UI_ScreenShot.texture = m_screenTex;
}
UpdateStateNode();
}
private void UpdateStateNode()
{
var stateType = MenuData.SavFile.GetState().GetType();
foreach (var item in m_stateNodes)
{
var type = item.Key;
var nodeGo = item.Value;
nodeGo.SetActiveEx(type == stateType);
}
}
public override void OnHide()
@ -60,7 +101,8 @@ namespace AxibugEmuOnline.Client
m_screenTex = null;
}
MenuData.SavFile.OnSavSuccessed -= RefreshUI;
MenuData.SavFile.OnSavSuccessed -= SavFile_OnSavSuccessed;
MenuData.SavFile.OnStateChanged -= UpdateStateNode;
}
public override void OnExecute(OptionUI optionUI, ref bool cancelHide)