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

Session rework #401

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
65c163f
Initial version of the sessions rework. Won't compile, as some things…
SokyranTheDragon Sep 30, 2023
7ac64b3
Merge branch 'master' of github.com:SokyranTheDragon/Multiplayer into…
SokyranTheDragon Oct 31, 2023
2bfbf44
First compiling version. Still WIP and needs cleaning.
SokyranTheDragon Nov 2, 2023
2598273
Turn pause locks into a session
SokyranTheDragon Nov 3, 2023
cd03e8c
Some minor cleanup related to trade sessions
SokyranTheDragon Nov 3, 2023
9561360
MpTradeSession.TryCreate now uses session manager
SokyranTheDragon Nov 4, 2023
9814dd0
Use `WorldComp.trading` instead of `WorldComp.sessionManager`
SokyranTheDragon Nov 4, 2023
23b6a96
Minor cleanup and error logging
SokyranTheDragon Nov 4, 2023
d4f363c
Fixed reading world data in `SemiPersistent:ReadSemiPersistent`
SokyranTheDragon Nov 4, 2023
8800453
TickMapTrading -> TickMapSessions
SokyranTheDragon Nov 4, 2023
54d201f
Remove the unnecessary "cleanup" of trading sessions
SokyranTheDragon Nov 5, 2023
e22f085
Fix ritual session not opening the dialog when started
SokyranTheDragon Nov 5, 2023
31750e0
Fix trading session transferables, put all drawn session changes into…
SokyranTheDragon Nov 5, 2023
21cc449
Rename `DrawnThingFilter` to `DrawnSessionWithTransferables`
SokyranTheDragon Nov 5, 2023
5acf4fe
Remove unnecessary commented-out code
SokyranTheDragon Nov 5, 2023
6b660ab
Call `ISession:PostRemoveSession` from `SessionManager:ExposeSessions…
SokyranTheDragon Nov 5, 2023
c2c11d9
Rename WindowSession back to Session
SokyranTheDragon Nov 5, 2023
237678d
Make session manager sync semi persistent session ID
SokyranTheDragon Nov 6, 2023
4f7f2ad
Stop sessions from self-assigning IDs, let session manager handle it
SokyranTheDragon Nov 6, 2023
5320d3f
Added XML documentation to most session interfaces
SokyranTheDragon Nov 6, 2023
a3438f8
ISemiPersistentSession Write/Read -> Sync, make rituals use it
SokyranTheDragon Nov 6, 2023
d1d5810
Slightly change how pause lock session works
SokyranTheDragon Nov 6, 2023
ceb1a83
Added ISessionManagerAPI
SokyranTheDragon Nov 6, 2023
d12a6cf
Expose `MultiplayerGameComp.nextSessionId`
SokyranTheDragon Nov 6, 2023
bc5d2e2
Rename `ISessionManagerAPI` to `ISessionManager`
SokyranTheDragon Nov 10, 2023
78bee79
Rename ISessionManager file
SokyranTheDragon Nov 10, 2023
e238082
Remove 3 session interfaces in favor of abstract classes
SokyranTheDragon Nov 10, 2023
f85c6b4
Added methods to `MultiplayerAPIBridge` that will (likely) end up in …
SokyranTheDragon Nov 11, 2023
ccf0b22
Fix session map syncing
SokyranTheDragon Nov 14, 2023
2a5ffe6
Merge branch 'master' of github.com:rwmt/Multiplayer into session-rework
SokyranTheDragon Nov 14, 2023
ba19d25
Added a sync dict entry for `ISessionWithTransferables`
SokyranTheDragon Nov 15, 2023
08600e8
Merge branch 'master' of github.com:SokyranTheDragon/Multiplayer into…
SokyranTheDragon Nov 20, 2023
2523da6
Merge branch 'master' of github.com:SokyranTheDragon/Multiplayer into…
SokyranTheDragon Dec 19, 2023
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
25 changes: 5 additions & 20 deletions Source/Client/AsyncTime/AsyncTimeComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,13 @@ public class AsyncTimeComp : IExposable, ITickable
{
public static Map tickingMap;
public static Map executingCmdMap;
public static List<PauseLockDelegate> pauseLocks = new();

public float TickRateMultiplier(TimeSpeed speed)
{
var comp = map.MpComp();

var enforcePause = comp.transporterLoading != null ||
comp.caravanForming != null ||
comp.ritualSession != null ||
comp.mapDialogs.Any() ||
Multiplayer.WorldComp.AnyTradeSessionsOnMap(map) ||
Multiplayer.WorldComp.splitSession != null ||
pauseLocks.Any(x => x(map));
var enforcePause = comp.sessionManager.IsAnySessionCurrentlyPausing(map) ||
Multiplayer.WorldComp.sessionManager.IsAnySessionCurrentlyPausing(map);

if (enforcePause)
return 0f;
Expand Down Expand Up @@ -113,7 +107,7 @@ public void Tick()
tickListRare.Tick();
tickListLong.Tick();

TickMapTrading();
TickMapSessions();

storyteller.StorytellerTick();
storyWatcher.StoryWatcherTick();
Expand All @@ -139,18 +133,9 @@ public void Tick()
}
}

public void TickMapTrading()
public void TickMapSessions()
{
var trading = Multiplayer.WorldComp.trading;

for (int i = trading.Count - 1; i >= 0; i--)
{
var session = trading[i];
if (session.playerNegotiator.Map != map) continue;

if (session.ShouldCancel())
Multiplayer.WorldComp.RemoveTradeSession(session);
}
map.MpComp().sessionManager.TickSessions();
}

// These are normally called in Map.MapUpdate() and react to changes in the game state even when the game is paused (not ticking)
Expand Down
5 changes: 2 additions & 3 deletions Source/Client/AsyncTime/AsyncWorldTimeComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ public float TickRateMultiplier(TimeSpeed speed)
{
if (Multiplayer.GameComp.asyncTime)
{
var enforcePause = Multiplayer.WorldComp.splitSession != null ||
AsyncTimeComp.pauseLocks.Any(x => x(null));
var enforcePause = Multiplayer.WorldComp.sessionManager.IsAnySessionCurrentlyPausing(null);

if (enforcePause)
return 0f;
Expand Down Expand Up @@ -104,7 +103,7 @@ public void Tick()
{
Find.TickManager.DoSingleTick();
worldTicks++;
Multiplayer.WorldComp.TickWorldTrading();
Multiplayer.WorldComp.TickWorldSessions();

if (ModsConfig.BiotechActive)
{
Expand Down
55 changes: 3 additions & 52 deletions Source/Client/AsyncTime/TimeControlUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,58 +358,9 @@ static void DrawWindowShortcuts(Rect button, Color bgColor, List<FloatMenuOption

static List<FloatMenuOption> GetBlockingWindowOptions(ColonistBar.Entry entry, ITickable tickable)
{
List<FloatMenuOption> options = new List<FloatMenuOption>();
var split = Multiplayer.WorldComp.splitSession;

if (split != null && split.Caravan.pawns.Contains(entry.pawn))
{
options.Add(new FloatMenuOption("MpCaravanSplittingSession".Translate(), () =>
{
SwitchToMapOrWorld(entry.map);
CameraJumper.TryJumpAndSelect(entry.pawn);
Multiplayer.WorldComp.splitSession.OpenWindow();
}));
}

if (Multiplayer.WorldComp.trading.FirstOrDefault(t => t.playerNegotiator?.Map == entry.map) is { } trade)
{
options.Add(new FloatMenuOption("MpTradingSession".Translate(), () =>
{
SwitchToMapOrWorld(entry.map);
CameraJumper.TryJumpAndSelect(trade.playerNegotiator);
Find.WindowStack.Add(new TradingWindow()
{ selectedTab = Multiplayer.WorldComp.trading.IndexOf(trade) });
}));
}

if (entry.map?.MpComp().transporterLoading != null)
{
options.Add(new FloatMenuOption("MpTransportLoadingSession".Translate(), () =>
{
SwitchToMapOrWorld(entry.map);
entry.map.MpComp().transporterLoading.OpenWindow();
}));
}

if (entry.map?.MpComp().caravanForming != null)
{
options.Add(new FloatMenuOption("MpCaravanFormingSession".Translate(), () =>
{
SwitchToMapOrWorld(entry.map);
entry.map.MpComp().caravanForming.OpenWindow();
}));
}

if (entry.map?.MpComp().ritualSession != null)
{
options.Add(new FloatMenuOption("MpRitualSession".Translate(), () =>
{
SwitchToMapOrWorld(entry.map);
entry.map.MpComp().ritualSession.OpenWindow();
}));
}

return options;
return Multiplayer.WorldComp.sessionManager.AllSessions
.ConcatIfNotNull(entry.map?.MpComp().sessionManager.AllSessions)
.Select(s => s.GetBlockingWindowOptions(entry)).Where(fmo => fmo != null).ToList();
}

static void SwitchToMapOrWorld(Map map)
Expand Down
2 changes: 2 additions & 0 deletions Source/Client/Comp/Game/MultiplayerGameComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class MultiplayerGameComp : IExposable, IHasSessionData
public PauseOnLetter pauseOnLetter;
public TimeControl timeControl;
public Dictionary<int, PlayerData> playerData = new(); // player id to player data
public int nextSessionId;

public string idBlockBase64;

Expand All @@ -32,6 +33,7 @@ public void ExposeData()
Scribe_Values.Look(ref logDesyncTraces, "logDesyncTraces");
Scribe_Values.Look(ref pauseOnLetter, "pauseOnLetter");
Scribe_Values.Look(ref timeControl, "timeControl");
Scribe_Values.Look(ref nextSessionId, "nextSessionId");

// Store for back-compat conversion in GameExposeComponentsPatch
if (Scribe.mode == LoadSaveMode.LoadingVars)
Expand Down
54 changes: 34 additions & 20 deletions Source/Client/Comp/Map/MultiplayerMapComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ public class MultiplayerMapComp : IExposable, IHasSessionData
public SortedDictionary<int, FactionMapData> factionData = new();
public SortedDictionary<int, CustomFactionMapData> customFactionData = new();

public CaravanFormingSession caravanForming;
public TransporterLoading transporterLoading;
public RitualSession ritualSession;
public SessionManager sessionManager;
public List<PersistentDialog> mapDialogs = new();
public int autosaveCounter;

Expand All @@ -34,26 +32,54 @@ public class MultiplayerMapComp : IExposable, IHasSessionData
public MultiplayerMapComp(Map map)
{
this.map = map;
sessionManager = new(map);
}

public CaravanFormingSession CreateCaravanFormingSession(bool reform, Action onClosed, bool mapAboutToBeRemoved, IntVec3? meetingSpot = null)
{
var caravanForming = sessionManager.GetFirstOfType<CaravanFormingSession>();
if (caravanForming == null)
{
caravanForming = new CaravanFormingSession(map, reform, onClosed, mapAboutToBeRemoved, meetingSpot);
if (!sessionManager.AddSession(caravanForming))
{
// Shouldn't happen if the session doesn't exist already, show an error just in case
Log.Error($"Failed trying to created a session of type {nameof(CaravanFormingSession)} - prior session did not exist and creating session failed.");
return null;
}
}
return caravanForming;
}

public TransporterLoading CreateTransporterLoadingSession(List<CompTransporter> transporters)
{
var transporterLoading = sessionManager.GetFirstOfType<TransporterLoading>();
if (transporterLoading == null)
{
transporterLoading = new TransporterLoading(map, transporters);
if (!sessionManager.AddSession(transporterLoading))
{
// Shouldn't happen if the session doesn't exist already, show an error just in case
Log.Error($"Failed trying to created a session of type {nameof(TransporterLoading)} - prior session did not exist and creating session failed.");
return null;
}
}
return transporterLoading;
}

public RitualSession CreateRitualSession(RitualData data)
{
var ritualSession = sessionManager.GetFirstOfType<RitualSession>();
if (ritualSession == null)
{
ritualSession = new RitualSession(map, data);
if (!sessionManager.AddSession(ritualSession))
{
// Shouldn't happen if the session doesn't exist already, show an error just in case
Log.Error($"Failed trying to created a session of type {nameof(RitualSession)} - prior session did not exist and creating session failed.");
return null;
}
}
return ritualSession;
}

Expand Down Expand Up @@ -115,8 +141,7 @@ public void ExposeData()
Scribe_Values.Look(ref isPlayerHome, "isPlayerHome", false, true);
}

Scribe_Deep.Look(ref caravanForming, "caravanFormingSession", map);
Scribe_Deep.Look(ref transporterLoading, "transporterLoading", map);
sessionManager.ExposeSessions();

Scribe_Collections.Look(ref mapDialogs, "mapDialogs", LookMode.Deep, map);
if (Scribe.mode == LoadSaveMode.LoadingVars && mapDialogs == null)
Expand Down Expand Up @@ -164,21 +189,14 @@ public void WriteSessionData(ByteWriter writer)
{
writer.WriteInt32(autosaveCounter);

writer.WriteBool(ritualSession != null);
ritualSession?.Write(writer);
sessionManager.WriteSemiPersistent(writer);
}

public void ReadSessionData(ByteReader reader)
{
autosaveCounter = reader.ReadInt32();

var hasRitual = reader.ReadBool();
if (hasRitual)
{
var session = new RitualSession(map);
session.Read(reader);
ritualSession = session;
}
sessionManager.ReadSemiPersistent(reader);
}

public int GetFactionId(ZoneManager zoneManager)
Expand Down Expand Up @@ -215,13 +233,9 @@ static void Prefix()
if (Multiplayer.Client == null) return;

// Trading window on resume save
if (Multiplayer.WorldComp.trading.NullOrEmpty()) return;

if (!Multiplayer.WorldComp.trading.NullOrEmpty()) return;
// playerNegotiator == null can only happen during loading? Is this a resuming check?
if (Multiplayer.WorldComp.trading.FirstOrDefault(t => t.playerNegotiator == null) is { } trade)
{
trade.OpenWindow();
}
Multiplayer.WorldComp.trading.FirstOrDefault(t => t.playerNegotiator == null)?.OpenWindow();
}
}

Expand Down
40 changes: 21 additions & 19 deletions Source/Client/Comp/World/MultiplayerWorldComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
using Verse;
using Multiplayer.Client.Persistent;
using Multiplayer.Client.Saving;
using Multiplayer.Common;

namespace Multiplayer.Client;

public class MultiplayerWorldComp
public class MultiplayerWorldComp : IHasSemiPersistentData

Check failure on line 12 in Source/Client/Comp/World/MultiplayerWorldComp.cs

View workflow job for this annotation

GitHub Actions / Builds

The type or namespace name 'IHasSemiPersistentData' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 12 in Source/Client/Comp/World/MultiplayerWorldComp.cs

View workflow job for this annotation

GitHub Actions / Builds

The type or namespace name 'IHasSemiPersistentData' could not be found (are you missing a using directive or an assembly reference?)
{
// SortedDictionary to ensure determinism
public SortedDictionary<int, FactionWorldData> factionData = new();

public World world;

public TileTemperaturesComp uiTemperatures;
public List<MpTradeSession> trading = new();
public CaravanSplittingSession splitSession;
public List<MpTradeSession> trading = new(); // Should only be modified from MpTradeSession in PostAdd/Remove and ExposeData
public SessionManager sessionManager = new(null);

public Faction spectatorFaction;

Expand All @@ -34,11 +35,10 @@
{
ExposeFactionData();

Scribe_Collections.Look(ref trading, "tradingSessions", LookMode.Deep);

if (Scribe.mode == LoadSaveMode.PostLoadInit)
if (trading.RemoveAll(t => t.trader == null || t.playerNegotiator == null) > 0)
Log.Message("Some trading sessions had null entries");
sessionManager.ExposeSessions();
// Ensure a pause lock session exists if there's any pause locks registered
if (!PauseLockSession.pauseLocks.NullOrEmpty())
sessionManager.AddSession(new PauseLockSession(null));

DoBackCompat();
}
Expand Down Expand Up @@ -116,23 +116,25 @@
}
}

public void TickWorldTrading()
public void WriteSemiPersistent(ByteWriter writer)
{
for (int i = trading.Count - 1; i >= 0; i--)
{
var session = trading[i];
if (session.playerNegotiator.Spawned) continue;
sessionManager.WriteSemiPersistent(writer);
}

if (session.ShouldCancel())
RemoveTradeSession(session);
}
public void ReadSemiPersistent(ByteReader data)
{
sessionManager.ReadSemiPersistent(data);
}

public void TickWorldSessions()
{
sessionManager.TickSessions();
}

public void RemoveTradeSession(MpTradeSession session)
{
int index = trading.IndexOf(session);
trading.Remove(session);
Find.WindowStack?.WindowOfType<TradingWindow>()?.Notify_RemovedSession(index);
// Cleanup and removal from `trading` field is handled in PostRemoveSession
sessionManager.RemoveSession(session);
}

public void SetFaction(Faction faction)
Expand Down
50 changes: 50 additions & 0 deletions Source/Client/Experimental/ISessionManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using Multiplayer.Client.Persistent;
using Verse;

namespace Multiplayer.Client.Experimental;

public interface ISessionManager
{
IReadOnlyList<Session> AllSessions { get; }
IReadOnlyList<ExposableSession> ExposableSessions { get; }
IReadOnlyList<SemiPersistentSession> SemiPersistentSessions { get; }
IReadOnlyList<ITickingSession> TickingSessions { get; }
bool AnySessionActive { get; }

/// <summary>
/// Adds a new session to the list of active sessions.
/// </summary>
/// <param name="session">The session to try to add to active sessions.</param>
/// <returns><see langword="true"/> if the session was added to active ones, <see langword="false"/> if there was a conflict between sessions.</returns>
bool AddSession(Session session);

/// <summary>
/// Tries to get a conflicting session (through the use of <see cref="ISessionWithCreationRestrictions"/>) or, if there was none, returns the input <paramref name="session"/>.
/// </summary>
/// <param name="session">The session to try to add to active sessions.</param>
/// <returns>A session that was conflicting with the input one, or the input itself if there were no conflicts. It may be of a different type than the input.</returns>
Session GetOrAddSessionAnyConflict(Session session);

/// <summary>
/// Tries to get a conflicting session (through the use of <see cref="ISessionWithCreationRestrictions"/>) or, if there was none, returns the input <paramref name="session"/>.
/// </summary>
/// <param name="session">The session to try to add to active sessions.</param>
/// <returns>A session that was conflicting with the input one if it's the same type (<c>other is T</c>), null if it's a different type, or the input itself if there were no conflicts.</returns>
T GetOrAddSession<T>(T session) where T : Session;

/// <summary>
/// Tries to remove a session from active ones.
/// </summary>
/// <param name="session">The session to try to remove from the active sessions.</param>
/// <returns><see langword="true"/> if successfully removed from <see cref="AllSessions"/>. Doesn't correspond to if it was successfully removed from other lists of sessions.</returns>
bool RemoveSession(Session session);

T GetFirstOfType<T>() where T : Session;

T GetFirstWithId<T>(int id) where T : Session;

Session GetFirstWithId(int id);

bool IsAnySessionCurrentlyPausing(Map map); // Is it necessary for the API?
}
Loading
Loading