Skip to content

Commit

Permalink
Preliminary refactor of save synchronisation
Browse files Browse the repository at this point in the history
  • Loading branch information
Extremelyd1 committed Jul 7, 2024
1 parent 9de4dac commit a7c2376
Show file tree
Hide file tree
Showing 10 changed files with 15,926 additions and 3,705 deletions.
42 changes: 37 additions & 5 deletions HKMP/Game/Client/Save/SaveDataMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using Hkmp.Collection;
using Hkmp.Logging;
using Hkmp.Util;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Hkmp.Game.Client.Save;

Expand Down Expand Up @@ -39,7 +41,7 @@ public static SaveDataMapping Instance {
/// Dictionary mapping player data values to booleans indicating whether they should be synchronised.
/// </summary>
[JsonProperty("playerData")]
public Dictionary<string, bool> PlayerDataBools { get; private set; }
public Dictionary<string, SyncProperties> PlayerDataBools { get; private set; }

/// <summary>
/// Bi-directional lookup that maps save data names and their indices.
Expand Down Expand Up @@ -72,14 +74,14 @@ public static SaveDataMapping Instance {
/// </summary>
#pragma warning disable 0649
[JsonProperty("persistentBoolItems")]
private readonly List<KeyValuePair<PersistentItemData, bool>> _persistentBoolsDataValues;
private readonly List<KeyValuePair<PersistentItemData, SyncProperties>> _persistentBoolsDataValues;
#pragma warning restore 0649

/// <summary>
/// Dictionary mapping persistent bool data values to booleans indicating whether they should be synchronised.
/// </summary>
[JsonIgnore]
public Dictionary<PersistentItemData, bool> PersistentBoolDataBools { get; private set; }
public Dictionary<PersistentItemData, SyncProperties> PersistentBoolDataBools { get; private set; }

/// <summary>
/// Bi-directional lookup that maps persistent bool names and their indices.
Expand All @@ -92,14 +94,14 @@ public static SaveDataMapping Instance {
/// </summary>
#pragma warning disable 0649
[JsonProperty("persistentIntItems")]
private readonly List<KeyValuePair<PersistentItemData, bool>> _persistentIntDataValues;
private readonly List<KeyValuePair<PersistentItemData, SyncProperties>> _persistentIntDataValues;
#pragma warning restore 0649

/// <summary>
/// Dictionary mapping persistent int data values to booleans indicating whether they should be synchronised.
/// </summary>
[JsonIgnore]
public Dictionary<PersistentItemData, bool> PersistentIntDataBools { get; private set; }
public Dictionary<PersistentItemData, SyncProperties> PersistentIntDataBools { get; private set; }

/// <summary>
/// Bi-directional lookup that maps persistent int names and their indices.
Expand Down Expand Up @@ -173,4 +175,34 @@ public void Initialize() {
PersistentIntDataIndices.Add(persistentIntData, index++);
}
}

/// <summary>
/// Properties that denote when to sync values.
/// </summary>
[UsedImplicitly]
internal class SyncProperties {
/// <summary>
/// Whether to sync this value. If true, the variable <seealso cref="SyncType"/> indicates where to store
/// the synced values.
/// </summary>
public bool Sync { get; set; }
/// <summary>
/// The sync type of this value. Type Player is used for player specific values and Server is used for
/// global world specific values.
/// </summary>
public SyncType SyncType { get; set; }
/// <summary>
/// Whether to ignore the check for scene host when sending/processing a save data for this value.
/// </summary>
public bool IgnoreSceneHost { get; set; }
}

/// <summary>
/// The sync type for sync properties indicating whether values are global or player specific.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
internal enum SyncType {
Player,
Server
}
}
92 changes: 72 additions & 20 deletions HKMP/Game/Client/Save/SaveManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,13 +369,23 @@ private void OnSceneChanged(Scene oldScene, Scene newScene) {
/// <param name="name">The name of the variable that was changed.</param>
/// <param name="encodeFunc">Function to encode the value of the variable to a byte array.</param>
private void CheckSendSaveUpdate(string name, Func<byte[]> encodeFunc) {
if (!_entityManager.IsSceneHost) {
Logger.Info($"Not scene host, not sending save update ({name})");
if (!_netClient.IsConnected) {
return;
}

if (!SaveDataMapping.PlayerDataBools.TryGetValue(name, out var syncProps)) {
Logger.Info($"Not in save data values, not sending save update ({name})");
return;
}

if (!SaveDataMapping.PlayerDataBools.TryGetValue(name, out var value) || !value) {
Logger.Info($"Not in save data values or false in save data values, not sending save update ({name})");
if (!syncProps.Sync) {
Logger.Info($"Value should not sync, not sending save update ({name})");
return;
}

// If we should do the scene host check and the player is not scene host, skip sending
if (!syncProps.IgnoreSceneHost && !_entityManager.IsSceneHost) {
Logger.Info($"Not scene host, but required, not sending save update ({name})");
return;
}

Expand Down Expand Up @@ -416,13 +426,17 @@ private void OnUpdatePersistents() {

Logger.Info($"Value for {itemData} changed to: {value}");

if (!_entityManager.IsSceneHost) {
Logger.Info(
$"Not scene host, not sending persistent int/geo rock save update ({itemData.Id}, {itemData.SceneName})");
if (!_netClient.IsConnected) {
continue;
}

if (SaveDataMapping.GeoRockDataBools.TryGetValue(itemData, out var shouldSync) && shouldSync) {
if (!_entityManager.IsSceneHost) {
Logger.Info(
$"Not scene host, not sending geo rock save update ({itemData.Id}, {itemData.SceneName})");
continue;
}

if (!SaveDataMapping.GeoRockDataIndices.TryGetValue(itemData, out var index)) {
Logger.Info(
$"Cannot find geo rock save data index, not sending save update ({itemData.Id}, {itemData.SceneName})");
Expand All @@ -435,7 +449,17 @@ private void OnUpdatePersistents() {
index,
new[] { (byte) value }
);
} else if (SaveDataMapping.PersistentIntDataBools.TryGetValue(itemData, out shouldSync) && shouldSync) {
} else if (
SaveDataMapping.PersistentIntDataBools.TryGetValue(itemData, out var syncProps) &&
syncProps.Sync
) {
// If we should do the scene host check and the player is not scene host, skip sending
if (!syncProps.IgnoreSceneHost && !_entityManager.IsSceneHost) {
Logger.Info(
$"Not scene host, not sending geo rock save update ({itemData.Id}, {itemData.SceneName})");
continue;
}

if (!SaveDataMapping.PersistentIntDataIndices.TryGetValue(itemData, out var index)) {
Logger.Info(
$"Cannot find persistent int save data index, not sending save update ({itemData.Id}, {itemData.SceneName})");
Expand All @@ -462,16 +486,22 @@ private void OnUpdatePersistents() {
var itemData = persistentFsmData.PersistentItemData;

Logger.Info($"Value for {itemData} changed to: {value}");

if (!_netClient.IsConnected) {
continue;
}

if (!_entityManager.IsSceneHost) {
if (!SaveDataMapping.PersistentBoolDataBools.TryGetValue(itemData, out var syncProps) ||
!syncProps.Sync) {
Logger.Info(
$"Not scene host, not sending geo rock save update ({itemData.Id}, {itemData.SceneName})");
$"Not in persistent bool save data values or false in sync props, not sending save update ({itemData.Id}, {itemData.SceneName})");
continue;
}

if (!SaveDataMapping.PersistentBoolDataBools.TryGetValue(itemData, out var shouldSync) || !shouldSync) {

// If we should do the scene host check and the player is not scene host, skip sending
if (!syncProps.IgnoreSceneHost && !_entityManager.IsSceneHost) {
Logger.Info(
$"Not in persistent bool save data values or false in save data values, not sending save update ({itemData.Id}, {itemData.SceneName})");
$"Not scene host, not sending persistent bool save update ({itemData.Id}, {itemData.SceneName})");
continue;
}

Expand Down Expand Up @@ -502,7 +532,15 @@ void CheckUpdates<TVar, TCheck>(
Func<TCheck, TCheck, bool> changeFunc
) {
foreach (var varName in variableNames) {
if (!SaveDataMapping.PlayerDataBools.TryGetValue(varName, out var shouldSync) || !shouldSync) {
if (!SaveDataMapping.PlayerDataBools.TryGetValue(varName, out var syncProps)) {
continue;
}

if (!syncProps.Sync) {
continue;
}

if (!syncProps.IgnoreSceneHost && !_entityManager.IsSceneHost) {
continue;
}

Expand All @@ -523,7 +561,7 @@ Func<TCheck, TCheck, bool> changeFunc

checkDict[varName] = newCheck;

if (_netClient.IsConnected && _entityManager.IsSceneHost) {
if (_netClient.IsConnected) {
_netClient.UpdateManager.SetSaveUpdate(
index,
EncodeValue(variable)
Expand Down Expand Up @@ -785,6 +823,7 @@ private void UpdateSaveWithData(ushort index, byte[] encodedValue) {
});
}

// TODO: refactor this, remove probably
if (index == SaveWarpIndex) {
// Specific handling of warp bench data
var respawnScene = DecodeString(encodedValue, 0);
Expand All @@ -811,10 +850,11 @@ string DecodeString(byte[] encoded, int startIndex) {
}

/// <summary>
/// Get the current save data as a dictionary with mapped indices and encoded values.
/// Get the current save data as a dictionary with mapped indices and encoded values. This only returns the
/// global save data for a server. E.g. broken walls, open doors, defeated bosses.
/// </summary>
/// <returns>A dictionary with mapped indices and byte-encoded values.</returns>
public static Dictionary<ushort, byte[]> GetCurrentSaveData() {
public static Dictionary<ushort, byte[]> GetCurrentGlobalSaveData() {
var pd = PlayerData.instance;
var sd = SceneData.instance;

Expand All @@ -823,15 +863,27 @@ public static Dictionary<ushort, byte[]> GetCurrentSaveData() {
void AddToSaveData<TCollection, TLookup>(
IEnumerable<TCollection> enumerable,
Func<TCollection, TLookup> keyFunc,
Dictionary<TLookup, bool> boolMapping,
object syncMapping,
BiLookup<TLookup, ushort> indexMapping,
Func<TCollection, object> valueFunc
) {
foreach (var collectionValue in enumerable) {
var key = keyFunc.Invoke(collectionValue);

if (!boolMapping.TryGetValue(key, out var shouldSync) || !shouldSync) {
continue;
if (syncMapping is Dictionary<TLookup, bool> boolMapping) {
if (!boolMapping.TryGetValue(key, out var shouldSync) || !shouldSync) {
continue;
}
} else if (syncMapping is Dictionary<TLookup, SaveDataMapping.SyncProperties> syncPropMapping) {
if (!syncPropMapping.TryGetValue(key, out var syncProps)) {
continue;
}

// Skip values that are not supposed to be synced, or ones that have the property that it is
// server data. Since we will not require the hosting player's save data on the server.
if (!syncProps.Sync || syncProps.SyncType != SaveDataMapping.SyncType.Server) {
continue;
}
}

if (!indexMapping.TryGetValue(key, out var index)) {
Expand Down
11 changes: 8 additions & 3 deletions HKMP/Game/GameManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ namespace Hkmp.Game;
/// Instantiates all necessary classes to start multiplayer activities.
/// </summary>
internal class GameManager {
/// <summary>
/// The server manager instance for the mod.
/// </summary>
public readonly ModServerManager ServerManager;

/// <summary>
/// Constructs this GameManager instance by instantiating all other necessary classes.
/// </summary>
Expand Down Expand Up @@ -41,17 +46,17 @@ public GameManager(ModSettings modSettings) {
netClient
);

var serverManager = new ModServerManager(
ServerManager = new ModServerManager(
netServer,
serverServerSettings,
packetManager,
uiManager
);
serverManager.Initialize();
ServerManager.Initialize();

new ClientManager(
netClient,
serverManager,
ServerManager,
packetManager,
uiManager,
clientServerSettings,
Expand Down
34 changes: 30 additions & 4 deletions HKMP/Game/Server/ModServerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ namespace Hkmp.Game.Server;
/// Specialization of <see cref="ServerManager"/> that adds handlers for the mod specific things.
/// </summary>
internal class ModServerManager : ServerManager {
/// <summary>
/// Save data that was loaded from selecting a save file. Will be retroactively applied to a server, if one was
/// requested to be started after selecting a save file.
/// </summary>
private ServerSaveData _loadedLocalSaveData;

public ModServerManager(
NetServer netServer,
ServerSettings serverSettings,
Expand All @@ -22,20 +28,40 @@ UiManager uiManager
ModHooks.FinishedLoadingModsHook += AddonManager.LoadAddons;

// Register handlers for UI events
uiManager.RequestServerStartHostEvent += port => {
CurrentSaveData = SaveManager.GetCurrentSaveData();
Start(port);
};
uiManager.RequestServerStartHostEvent += OnRequestServerStartHost;
uiManager.RequestServerStopHostEvent += Stop;

// Register application quit handler
ModHooks.ApplicationQuitHook += Stop;
}

private void OnRequestServerStartHost(int port) {
// Get the global save data from the save manager, which obtains the global save data from the loaded
// save file that the user selected. Then we import the player save data from the (potentially) loaded
// modded save file from the user selected save file.
ServerSaveData = new ServerSaveData {
GlobalSaveData = SaveManager.GetCurrentGlobalSaveData()
};

if (_loadedLocalSaveData != null) {
ServerSaveData.PlayerSaveData = _loadedLocalSaveData.PlayerSaveData;
}

Start(port);
}

/// <inheritdoc />
protected override void RegisterCommands() {
base.RegisterCommands();

CommandManager.RegisterCommand(new SettingsCommand(this, InternalServerSettings));
}

public void OnLoadLocal(ServerSaveData serverSaveData) {
_loadedLocalSaveData = serverSaveData;
}

public ServerSaveData OnSaveLocal() {
return ServerSaveData;
}
}
Loading

0 comments on commit a7c2376

Please sign in to comment.