diff --git a/Source/Client/AsyncTime/AsyncWorldTimeComp.cs b/Source/Client/AsyncTime/AsyncWorldTimeComp.cs index 6be82d6af..8184163ab 100644 --- a/Source/Client/AsyncTime/AsyncWorldTimeComp.cs +++ b/Source/Client/AsyncTime/AsyncWorldTimeComp.cs @@ -276,7 +276,10 @@ public void ExecuteCmd(ScheduledCommand cmd) private static void CreateJoinPointAndSendIfHost() { - Multiplayer.session.dataSnapshot = SaveLoad.CreateGameDataSnapshot(SaveLoad.SaveAndReload(true), Multiplayer.GameComp.multifaction); + Multiplayer.session.dataSnapshot = SaveLoad.SaveReloadAndCreateSnapshot( + Multiplayer.GameComp.multifaction, + ReloadOptimizationMode.ForJoinPointSnapshot + ); if (!TickPatch.Simulating && !Multiplayer.IsReplay) { diff --git a/Source/Client/Saving/CacheForReloading.cs b/Source/Client/Saving/CacheForReloading.cs deleted file mode 100644 index 0eee71f2e..000000000 --- a/Source/Client/Saving/CacheForReloading.cs +++ /dev/null @@ -1,164 +0,0 @@ -using HarmonyLib; -using Multiplayer.Client.Util; -using RimWorld.Planet; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Verse; - -// TODO: TEST: Test that this works with the new world generation - -namespace Multiplayer.Client -{ - [HarmonyPatch(typeof(MapDrawer), nameof(MapDrawer.RegenerateEverythingNow))] - public static class MapDrawerRegenPatch - { - public static Dictionary copyFrom = new(); - - // These are readonly so they need to be set using reflection - private static FieldInfo mapDrawerMap = AccessTools.Field(typeof(MapDrawer), nameof(MapDrawer.map)); - private static FieldInfo sectionMap = AccessTools.Field(typeof(Section), nameof(Section.map)); - - static bool Prefix(MapDrawer __instance) - { - Map map = __instance.map; - if (!copyFrom.TryGetValue(map.uniqueID, out MapDrawer keepDrawer)) return true; - - map.mapDrawer = keepDrawer; - mapDrawerMap.SetValue(keepDrawer, map); - - foreach (Section section in keepDrawer.sections) - { - sectionMap.SetValue(section, map); - - for (int i = 0; i < section.layers.Count; i++) - { - SectionLayer layer = section.layers[i]; - - if (!ShouldKeep(layer)) - section.layers[i] = (SectionLayer)Activator.CreateInstance(layer.GetType(), section); - else if (layer is SectionLayer_TerrainScatter scatter) - scatter.scats.Do(s => s.map = map); - } - } - - foreach (Section s in keepDrawer.sections) - foreach (SectionLayer layer in s.layers) - if (!ShouldKeep(layer)) - layer.Regenerate(); - - copyFrom.Remove(map.uniqueID); - - return false; - } - - static bool ShouldKeep(SectionLayer layer) - { - return layer.GetType().Assembly == typeof(Game).Assembly; - } - } - - [HarmonyPatch(typeof(WorldGrid), MethodType.Constructor)] - public static class WorldGridCachePatch - { - public static AccessTools.FieldRef> globalLayers = AccessTools.FieldRefAccess>(nameof(WorldGrid.globalLayers)); - public static WorldGrid copyFrom; - - static bool Prefix(WorldGrid __instance, ref int ___cachedTraversalDistance, ref int ___cachedTraversalDistanceForStart, ref int ___cachedTraversalDistanceForEnd) - { - if (copyFrom == null) return true; - - WorldGrid grid = __instance; - - grid.surfaceViewAngle = copyFrom.SurfaceViewAngle; - grid.surfaceViewCenter = copyFrom.SurfaceViewCenter; - grid.surface.verts = copyFrom.UnsafeVerts; - grid.surface.tileIDToNeighbors_offsets = copyFrom.UnsafeTileIDToNeighbors_offsets; - grid.surface.tileIDToNeighbors_values = copyFrom.UnsafeTileIDToNeighbors_values; - grid.surface.tileIDToVerts_offsets = copyFrom.UnsafeTileIDToVerts_offsets; - grid.surface.averageTileSize = copyFrom.AverageTileSize; - grid.surface.tiles.Clear(); - globalLayers(grid) = copyFrom.globalLayers; - - ___cachedTraversalDistance = -1; - ___cachedTraversalDistanceForStart = -1; - ___cachedTraversalDistanceForEnd = -1; - - copyFrom = null; - - return false; - } - } - - [HarmonyPatch(typeof(WorldGrid), nameof(WorldGrid.ExposeData))] - public static class WorldGridExposeDataPatch - { - public static WorldGrid copyFrom; - - static bool Prefix(WorldGrid __instance) - { - if (copyFrom == null) return true; - - WorldGrid grid = __instance; - - List copyTiles = copyFrom.Tiles.ToList(); - List gridTiles = grid.Tiles.ToList(); - - for(int i = 0; i < copyTiles.Count; i++) - { - SurfaceTile sourceTile = copyTiles[i]; - SurfaceTile targetTile = gridTiles[i]; - - // Tile - targetTile.biome = sourceTile.biome; - targetTile.elevation = sourceTile.elevation; - targetTile.hilliness = sourceTile.hilliness; - targetTile.temperature = sourceTile.temperature; - targetTile.rainfall = sourceTile.rainfall; - targetTile.swampiness = sourceTile.swampiness; - targetTile.feature = sourceTile.feature; - targetTile.pollution = sourceTile.pollution; - targetTile.tile = sourceTile.tile; - targetTile.mutatorsNullable = sourceTile.mutatorsNullable; - - // Surface Tile - Roads/Rivers are getters for potentialRoads/potentialRivers - targetTile.potentialRoads = sourceTile.potentialRoads; - targetTile.riverDist = sourceTile.riverDist; - targetTile.potentialRivers = sourceTile.potentialRivers; - } - - // This is plain old data apart from the WorldFeature feature field which is a reference - // It later gets reset in WorldFeatures.ExposeData though so it can be safely copied - - // Use Clear/AddRange instead of reflection to preserve collection observers - // and handle readonly field correctly - grid.surface.tiles.Clear(); - grid.surface.tiles.AddRange(copyFrom.surface.tiles); - - // ExposeData runs multiple times but WorldGrid only needs LoadSaveMode.LoadingVars - copyFrom = null; - - return false; - } - } - - //TODO: TEST: Test that this works with the new world generation - [HarmonyPatch(typeof(WorldGrid), (nameof(WorldGrid.InitializeGlobalLayers)))] - public static class WorldRendererCachePatch - { - - public static AccessTools.FieldRef> globalLayers = AccessTools.FieldRefAccess>(nameof(WorldGrid.globalLayers)); - public static WorldGrid copyFrom; - - static bool Prefix(WorldGrid __instance) - { - if (copyFrom == null) return true; - - globalLayers(__instance) = copyFrom.globalLayers; - copyFrom = null; - - return false; - } - } -} diff --git a/Source/Client/Saving/ReloadOptimization.cs b/Source/Client/Saving/ReloadOptimization.cs new file mode 100644 index 000000000..5b907e6ae --- /dev/null +++ b/Source/Client/Saving/ReloadOptimization.cs @@ -0,0 +1,53 @@ +using System; +using Verse; + +namespace Multiplayer.Client +{ + public enum ReloadOptimizationMode + { + None, + ForJoinPointSnapshot, + } + + internal static class ReloadOptimization + { + public static ReloadOptimizationPlan PlanFor(ReloadOptimizationMode mode) + { + return mode switch + { + ReloadOptimizationMode.ForJoinPointSnapshot => new( + RegenerateMapDrawersWhenRestoringFaction: false, + RegenerateMapDrawersAfterSnapshot: true + ), + _ => new( + RegenerateMapDrawersWhenRestoringFaction: true, + RegenerateMapDrawersAfterSnapshot: false + ), + }; + } + + public static void Complete(ReloadOptimizationMode mode) + { + Complete(mode, RegenerateMapDrawers); + } + + internal static void Complete(ReloadOptimizationMode mode, Action regenerateMapDrawers) + { + if (!PlanFor(mode).RegenerateMapDrawersAfterSnapshot) + return; + + regenerateMapDrawers(); + } + + private static void RegenerateMapDrawers() + { + foreach (var map in Find.Maps) + map.mapDrawer.RegenerateEverythingNow(); + } + } + + internal readonly record struct ReloadOptimizationPlan( + bool RegenerateMapDrawersWhenRestoringFaction, + bool RegenerateMapDrawersAfterSnapshot + ); +} diff --git a/Source/Client/Saving/SaveLoad.cs b/Source/Client/Saving/SaveLoad.cs index a625b5669..857ff0b18 100644 --- a/Source/Client/Saving/SaveLoad.cs +++ b/Source/Client/Saving/SaveLoad.cs @@ -20,13 +20,23 @@ public record TempGameData(XmlDocument SaveData, byte[] SessionData); public static class SaveLoad { - public static TempGameData SaveAndReload(bool cache = false) + public static TempGameData SaveAndReload() + { + return SaveAndReload(ReloadOptimizationMode.None); + } + + public static TempGameData SaveAndReload(ReloadOptimizationMode optimizationMode) + { + var data = SaveAndReloadCore(optimizationMode); + ReloadOptimization.Complete(optimizationMode); + return data; + } + + private static TempGameData SaveAndReloadCore(ReloadOptimizationMode optimizationMode) { Multiplayer.reloading = true; - var worldGridSaved = Find.WorldGrid; var tweenedPos = new Dictionary(); - var drawers = new Dictionary(); var localFactionId = Multiplayer.RealPlayerFaction.loadID; var mapCmds = new Dictionary>(); var planetRenderMode = Find.World.renderer.wantedMode; @@ -37,8 +47,6 @@ public static TempGameData SaveAndReload(bool cache = false) foreach (Map map in Find.Maps) { - drawers[map.uniqueID] = map.mapDrawer; - foreach (Pawn p in map.mapPawns.AllPawnsSpawned) tweenedPos[p.thingIDNumber] = p.drawer.tweener.tweenedPos; @@ -58,21 +66,6 @@ public static TempGameData SaveAndReload(bool cache = false) gameData = SaveGameData(); } - if (cache) - { - MapDrawerRegenPatch.copyFrom = drawers; - WorldGridCachePatch.copyFrom = worldGridSaved; - WorldGridExposeDataPatch.copyFrom = worldGridSaved; - WorldRendererCachePatch.copyFrom = worldGridSaved; - } - else - { - MapDrawerRegenPatch.copyFrom.Clear(); - WorldGridCachePatch.copyFrom = null; - WorldGridExposeDataPatch.copyFrom = null; - WorldRendererCachePatch.copyFrom = null; - } - MusicManagerPlay musicManager = null; if (Find.MusicManagerPlay.gameObjectCreated) { @@ -88,7 +81,11 @@ public static TempGameData SaveAndReload(bool cache = false) if (musicManager != null) Current.Root_Play.musicManagerPlay = musicManager; - Multiplayer.game.ChangeRealPlayerFaction(Find.FactionManager.GetById(localFactionId)); + var reloadPlan = ReloadOptimization.PlanFor(optimizationMode); + Multiplayer.game.ChangeRealPlayerFaction( + Find.FactionManager.GetById(localFactionId), + reloadPlan.RegenerateMapDrawersWhenRestoringFaction + ); foreach (Map m in Find.Maps) { @@ -118,6 +115,14 @@ public static TempGameData SaveAndReload(bool cache = false) return gameData; } + public static GameDataSnapshot SaveReloadAndCreateSnapshot(bool removeCurrentMapId, ReloadOptimizationMode optimizationMode) + { + var data = SaveAndReloadCore(optimizationMode); + var snapshot = CreateGameDataSnapshot(data, removeCurrentMapId); + ReloadOptimization.Complete(optimizationMode); + return snapshot; + } + public static void LoadInMainThread(TempGameData gameData) { DeepProfiler.Start("Multiplayer LoadInMainThread");