Skip to content

Commit

Permalink
Merge pull request #640 from kianzarrin/Hot-reload
Browse files Browse the repository at this point in the history
fixed Reload, Hot-Swap, Memleaks:
- #211 : in-game Hot-Swap supported (with assembly version display)
- Fixed Loading another game while in game creates multiple instances of UI.
- Fixed TMPE memory leaks in cleanup path.
- Post build events prints time and assembly version.
  • Loading branch information
kianzarrin authored Feb 8, 2020
2 parents 24fd36a + 931dba2 commit 630a26b
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 103 deletions.
178 changes: 105 additions & 73 deletions TLM/TLM/LoadingExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,30 @@ namespace TrafficManager {
using TrafficManager.State;
using TrafficManager.UI.Localization;
using TrafficManager.UI;
using static TrafficManager.Util.Shortcuts;
using UnityEngine;

[UsedImplicitly]
public class LoadingExtension : LoadingExtensionBase {
private const string HARMONY_ID = "de.viathinksoft.tmpe";
internal static LoadingExtension Instance = null;

internal bool InGameHotReload { get; private set; } = false;

internal static AppMode currentMode => SimulationManager.instance.m_ManagersWrapper.loading.currentMode;

internal static bool InGame() {
try {
return currentMode == AppMode.Game;
} catch {
return false;
}
}

FastList<ISimulationManager> simManager =>
typeof(SimulationManager).GetField("m_managers", BindingFlags.Static | BindingFlags.NonPublic)
?.GetValue(null) as FastList<ISimulationManager>;


public class Detour {
public MethodInfo OriginalMethod;
Expand All @@ -40,21 +60,13 @@ public class ManualHarmonyPatch {
public HarmonyMethod postfix = null;
}

// public static LoadingExtension Instance;

public static bool IsPathManagerReplaced {
get; private set;
}

public static CustomPathManager CustomPathManager { get; set; }

public static bool DetourInited { get; set; }

public static List<Detour> Detours { get; set; }

public static HarmonyInstance HarmonyInst { get; private set; }
// public static TrafficManagerMode ToolMode { get; set; }
// public static TrafficManagerTool TrafficManagerTool { get; set; }

/// <summary>
/// Contains loaded languages and lookup functions for text translations
Expand Down Expand Up @@ -261,6 +273,8 @@ private void InitDetours() {
}

public override void OnCreated(ILoading loading) {
Log._Debug("LoadingExtension.OnCreated() called");

// SelfDestruct.DestructOldInstances(this);
base.OnCreated(loading);

Expand All @@ -270,6 +284,9 @@ public override void OnCreated(ILoading loading) {
CustomPathManager = new CustomPathManager();

RegisterCustomManagers();

Instance = this;
InGameHotReload = InGame();
}

private void RegisterCustomManagers() {
Expand Down Expand Up @@ -304,22 +321,15 @@ private void RegisterCustomManagers() {
}

public override void OnReleased() {
Instance = null;
base.OnReleased();

UIBase.ReleaseTool();
}

public override void OnLevelUnloading() {
Log.Info("OnLevelUnloading");
base.OnLevelUnloading();
if (IsPathManagerReplaced) {
CustomPathManager._instance.WaitForAllPaths();
}

// Object.Destroy(BaseUI);
// BaseUI = null;
// Object.Destroy(TransportDemandUI);
// TransportDemandUI = null;
CustomPathManager._instance.WaitForAllPaths();

try {
var reverseManagers = new List<ICustomManager>(RegisteredManagers);
Expand All @@ -333,21 +343,38 @@ public override void OnLevelUnloading() {
Flags.OnLevelUnloading();
GlobalConfig.OnLevelUnloading();

var gameObject = UIView.GetAView().gameObject;
void Destroy<T>() where T : MonoBehaviour {
Object obj = (Object)gameObject.GetComponent<T>();
if (obj != null) {
Object.Destroy(obj);
}
}

// remove vehicle button
var removeVehicleButtonExtender = UIView
.GetAView().gameObject
.GetComponent<RemoveVehicleButtonExtender>();
if (removeVehicleButtonExtender != null) {
Object.Destroy(removeVehicleButtonExtender, 10f);
Destroy<RemoveVehicleButtonExtender>();
Destroy<RemoveCitizenInstanceButtonExtender>();

// Custom path manger is destroyed when reloading. That is why the following code
// is commented out.
//simManager?.Remove(CustomPathManager);
//Object.Destroy(CustomPathManager);
//CustomPathManager = null;

if (TransportDemandUI != null) {
UIView uiView = UIView.GetAView();
Object.Destroy(TransportDemandUI);
TransportDemandUI = null;
}

// remove citizen instance button
var removeCitizenInstanceButtonExtender = UIView
.GetAView().gameObject
.GetComponent<RemoveCitizenInstanceButtonExtender>();
if (removeCitizenInstanceButtonExtender != null) {
Object.Destroy(removeCitizenInstanceButtonExtender, 10f);
Log.Info("Removing Controls from UI.");
if (BaseUI != null) {
BaseUI.Close(); // Hide the UI ASAP
Object.Destroy(BaseUI);
BaseUI = null;
Log._Debug("removed UIBase instance.");
}

#if TRACE
Singleton<CodeProfiler>.instance.OnLevelUnloading();
#endif
Expand Down Expand Up @@ -452,63 +479,68 @@ public override void OnLevelLoaded(LoadMode mode) {
}
}

if (!IsPathManagerReplaced) {
try {
Log.Info("Pathfinder Compatible. Setting up CustomPathManager and SimManager.");
FieldInfo pathManagerInstance = typeof(Singleton<PathManager>).GetField(
"sInstance",
BindingFlags.Static | BindingFlags.NonPublic);
try {
Log.Info("Pathfinder Compatible. Setting up CustomPathManager and SimManager.");
FieldInfo pathManagerInstance = typeof(Singleton<PathManager>).GetField(
"sInstance",
BindingFlags.Static | BindingFlags.NonPublic);
if (pathManagerInstance == null) {
throw new Exception("pathManagerInstance is null");
}

PathManager stockPathManager = PathManager.instance;
Log._Debug($"Got stock PathManager instance {stockPathManager.GetName()}");

CustomPathManager = stockPathManager.gameObject.AddComponent<CustomPathManager>();
Log._Debug("Added CustomPathManager to gameObject List");
PathManager stockPathManager = PathManager.instance;
if (stockPathManager == null) {
throw new Exception("stockPathManager is null");
}

if (CustomPathManager == null) {
Log.Error("CustomPathManager null. Error creating it.");
return;
}
Log._Debug($"Got stock PathManager instance {stockPathManager?.GetName()}");

CustomPathManager.UpdateWithPathManagerValues(stockPathManager);
Log._Debug("UpdateWithPathManagerValues success");
CustomPathManager = stockPathManager.gameObject.AddComponent<CustomPathManager>();
Log._Debug("Added CustomPathManager to gameObject List");

pathManagerInstance?.SetValue(null, CustomPathManager);
if (CustomPathManager == null) {
Log.Error("CustomPathManager null. Error creating it.");
return;
}

Log._Debug("Getting Current SimulationManager");
var simManager =
typeof(SimulationManager).GetField(
"m_managers",
BindingFlags.Static | BindingFlags.NonPublic)
?.GetValue(null) as FastList<ISimulationManager>;
CustomPathManager.UpdateWithPathManagerValues(stockPathManager);
Log._Debug("UpdateWithPathManagerValues success");

Log._Debug("Removing Stock PathManager");
simManager?.Remove(stockPathManager);
pathManagerInstance.SetValue(null, CustomPathManager);

Log._Debug("Adding Custom PathManager");
simManager?.Add(CustomPathManager);
Log._Debug("Getting Current SimulationManager");
var simManager = this.simManager;
if (simManager == null) {
throw new Exception("simManager is null");
}

Object.Destroy(stockPathManager, 10f);
Log._Debug("Removing Stock PathManager");
simManager.Remove(stockPathManager);

Log._Debug("Should be custom: " + Singleton<PathManager>.instance.GetType());
Log._Debug("Adding Custom PathManager");
simManager.Add(CustomPathManager);

IsPathManagerReplaced = true;
} catch (Exception ex) {
string error = "Traffic Manager: President Edition failed to load. You can continue " +
"playing but it's NOT recommended. Traffic Manager will not work as expected.";
Log.Error(error);
Log.Error($"Path manager replacement error: {ex}");
Object.Destroy(stockPathManager, 10f);

Singleton<SimulationManager>.instance.m_ThreadingWrapper.QueueMainThread(
() => {
UIView.library
.ShowModal<ExceptionPanel>("ExceptionPanel")
.SetMessage(
"TM:PE failed to load",
error,
true);
});
}
Log._Debug("Should be custom: " + Singleton<PathManager>.instance.GetType());

}
catch (Exception ex) {
string error = "Traffic Manager: President Edition failed to load. You can continue " +
"playing but it's NOT recommended. Traffic Manager will not work as expected.";
Log.Error(error);
Log.Error($"Path manager replacement error: {ex}");

Singleton<SimulationManager>.instance.m_ThreadingWrapper.QueueMainThread(
() => {
UIView.library
.ShowModal<ExceptionPanel>("ExceptionPanel")
.SetMessage(
"TM:PE failed to load",
error,
true);
});
}

Log.Info("Adding Controls to UI.");
Expand Down
10 changes: 9 additions & 1 deletion TLM/TLM/State/SerializableDataExtension.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace TrafficManager.State {
namespace TrafficManager.State {
using CSUtil.Commons;
using ICities;
using JetBrains.Annotations;
Expand All @@ -21,6 +21,14 @@ public class SerializableDataExtension

public override void OnCreated(ISerializableData serializableData) {
_serializableData = serializableData;

Log._Debug("SerializableDataExtension.OnCreated() called");

if (LoadingExtension.Instance.InGameHotReload) {
Log._Debug("HOT RELOAD ...");
OnLoadData();
LoadingExtension.Instance.OnLevelLoaded(LoadMode.LoadGame);
}
}

public override void OnReleased() { }
Expand Down
28 changes: 20 additions & 8 deletions TLM/TLM/TLM.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -521,8 +521,22 @@
<EmbeddedResource Include="Resources\Translations\VehicleRestrictions.csv" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="PostBuildMacros">
<!-- Code from https://superuser.com/questions/82231/how-do-i-do-comments-at-a-windows-command-prompt -->
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="Targets" />
</GetAssemblyIdentity>
<ItemGroup>
<VersionNumber Include="@(Targets->'%(Version)')" />
</ItemGroup>
</Target>
<PropertyGroup>
<PostBuildEvent>rem set "DEPLOYDIR=D:\Games\Steam\steamapps\workshop\content\255710\1637663252\"
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
PostBuildMacros;
</PostBuildEventDependsOn>
<PostBuildEvent>
rem set "DEPLOYDIR=D:\Games\Steam\steamapps\workshop\content\255710\1637663252\"
set "DEPLOYDIR=$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)\"

del /Q "%25DEPLOYDIR%25*"
Expand All @@ -534,14 +548,12 @@ xcopy /y "$(TargetDir)CSUtil.CameraControl.dll" "%25DEPLOYDIR%25"
xcopy /y "$(TargetDir)TMPE.RedirectionFramework.dll" "%25DEPLOYDIR%25"
xcopy /y "$(TargetDir)CSUtil.Commons.dll" "%25DEPLOYDIR%25"
xcopy /y "$(TargetDir)0TMPE.Harmony.dll" "%25DEPLOYDIR%25"

set DEPLOYDIR=</PostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
echo THE ASSEMBLY VERSION IS: @(VersionNumber) created at %25time%25

set DEPLOYDIR=
</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
Expand Down
8 changes: 8 additions & 0 deletions TLM/TLM/TrafficManagerMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace TrafficManager {
using TrafficManager.State;
using TrafficManager.UI;
using TrafficManager.Util;
using static TrafficManager.Util.Shortcuts;
using ColossalFramework;

public class TrafficManagerMod : IUserMod {
#if LABS
Expand Down Expand Up @@ -76,6 +78,12 @@ public void OnDisabled() {
LoadingManager.instance.m_introLoaded -= CheckForIncompatibleMods;
LocaleManager.eventLocaleChanged -= Translation.HandleGameLocaleChange;
Translation.IsListeningToGameLocaleChanged = false; // is this necessary?

if (LoadingExtension.InGame() && LoadingExtension.Instance != null) {
//Hot reload Unloading
LoadingExtension.Instance.OnLevelUnloading();
LoadingExtension.Instance.OnReleased();
}
}

[UsedImplicitly]
Expand Down
8 changes: 8 additions & 0 deletions TLM/TLM/UI/MainMenu/VersionLabel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace TrafficManager.UI.MainMenu {
using System.Reflection;
using ColossalFramework.UI;
using UnityEngine;

Expand All @@ -9,6 +10,13 @@ public override void Start() {
MainMenuPanel.SIZE_PROFILES[0].MENU_WIDTH,
MainMenuPanel.SIZE_PROFILES[0].TOP_BORDER);
text = TrafficManagerMod.ModName;

if(LoadingExtension.Instance.InGameHotReload) {
// make it easier to Identify Hot reload.
text += " HOT RELOAD " +
Assembly.GetExecutingAssembly().GetName().Version;
}

relativePosition = new Vector3(5f, 5f);
textAlignment = UIHorizontalAlignment.Left;
}
Expand Down
8 changes: 7 additions & 1 deletion TLM/TLM/UI/TrafficManagerTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,17 @@ public class TrafficManagerTool
ExtVehicleType.Service, ExtVehicleType.RailVehicle
};

private static SubTool _activeSubTool;
private SubTool _activeSubTool;

private static IDisposable _confDisposable;

static TrafficManagerTool() { }

protected override void OnDestroy() {
Log.Info("TrafficManagerTool.OnDestroy() called");
base.OnDestroy();
}

internal ToolController GetToolController() {
return m_toolController;
}
Expand Down Expand Up @@ -176,6 +181,7 @@ internal void Initialize() {
Log.Info("TrafficManagerTool: Initialization completed.");
}


public void OnUpdate(GlobalConfig config) {
InitializeSubTools();
}
Expand Down
Loading

0 comments on commit 630a26b

Please sign in to comment.