Skip to content

Commit

Permalink
#260 Add enhanced loading overlay
Browse files Browse the repository at this point in the history
  • Loading branch information
polycone committed Oct 15, 2023
1 parent c93da3f commit 477f296
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ public void EstablishingTwoPlayersConnection() {
// Event: the client finished loading and the game is started
clientRuntime.StartGame();

// Lists must be equal and the client player must be in loading state
AssertPlayersAreEqual(hostRuntime, clientRuntime);
Assert.AreEqual(expected: PlayerState.Loading, actual: clientPlayer.State);

// Unity transitions into the next frame
UnityTestRuntime.NextFrame();

// Lists must be equal and the client player must be ready
AssertPlayersAreEqual(hostRuntime, clientRuntime);
Assert.AreEqual(expected: PlayerState.Ready, actual: clientPlayer.State);
Expand Down Expand Up @@ -158,7 +165,13 @@ private static void AssertPlayersAreEqual(TestRuntime runtimeA, TestRuntime runt
private static Harmony SetupEnvironment() {
UnityTestRuntime.Install();
var harmony = new Harmony("MultiplayerConnectionTests");
PatchesSetup.Install(harmony, new List<Type> { typeof(WorldManagerPatch), typeof(LoadOverlayPatch) });
PatchesSetup.Install(
harmony,
new List<Type> {
typeof(WorldManagerPatch),
typeof(MultiplayerStatusOverlayPatch)
}
);
return harmony;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
using HarmonyLib;
using JetBrains.Annotations;
using MultiplayerMod.Multiplayer.UI;
using MultiplayerMod.Multiplayer.UI.Overlays;

namespace MultiplayerMod.Test.Multiplayer;

[UsedImplicitly]
[HarmonyPatch(typeof(LoadOverlay))]
public class LoadOverlayPatch {
[HarmonyPatch(typeof(MultiplayerStatusOverlay))]
public class MultiplayerStatusOverlayPatch {

[UsedImplicitly]
[HarmonyPrefix]
[HarmonyPatch(nameof(LoadOverlay.Show))]
[HarmonyPatch(nameof(MultiplayerStatusOverlay.Show))]
private static bool Show() {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/MultiplayerMod.Test/Multiplayer/MultiplayerTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ public static TestRuntime CreateTestRuntime(MultiplayerMode mode, string playerN
.AddType<Recorders>()
.AddType<TestRuntime>();

var configurer = new MultiplayerCommandsConfigurer();
configurer.Configure(builder);
new MultiplayerCommandsConfigurer().Configure(builder);
new UnityTaskSchedulerConfigurer().Configure(builder);

var container = builder.Build();
var runtime = container.Get<TestRuntime>();
Expand Down
10 changes: 0 additions & 10 deletions src/MultiplayerMod/Multiplayer/Commands/Overlay/ShowLoadOverlay.cs

This file was deleted.

4 changes: 3 additions & 1 deletion src/MultiplayerMod/Multiplayer/Commands/World/LoadWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public LoadWorld(string worldName, byte[] data) {
this.data = data;
}

public override void Execute(MultiplayerCommandContext context) => WorldManager.LoadWorldSave(worldName, data);
public override void Execute(MultiplayerCommandContext context) {
context.Runtime.Dependencies.Get<WorldManager>().RequestWorldLoad(worldName, data);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using MultiplayerMod.Core.Events;
using MultiplayerMod.Multiplayer.CoreOperations.Events;
using MultiplayerMod.Multiplayer.Players.Events;
using MultiplayerMod.Multiplayer.UI;
using MultiplayerMod.Multiplayer.UI.Overlays;
using MultiplayerMod.Network;

namespace MultiplayerMod.Multiplayer.CoreOperations;
Expand All @@ -23,7 +23,7 @@ public MultiplayerJoinRequestController(EventDispatcher events, IMultiplayerClie

private void OnMultiplayerJoinRequested(MultiplayerJoinRequestedEvent @event) {
events.Dispatch(new MultiplayerModeSelectedEvent(MultiplayerMode.Client));
LoadOverlay.Show($"Connecting to {@event.HostName}...");
MultiplayerStatusOverlay.Show($"Connecting to {@event.HostName}...");
client.Connect(@event.Endpoint);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
using MultiplayerMod.Core.Dependency;
using MultiplayerMod.Core.Events;
using MultiplayerMod.Multiplayer.CoreOperations.Events;
using MultiplayerMod.Multiplayer.UI;
using MultiplayerMod.Multiplayer.Players.Events;
using MultiplayerMod.Multiplayer.UI.Overlays;
using MultiplayerMod.Network;

namespace MultiplayerMod.Multiplayer.CoreOperations;
Expand All @@ -12,10 +13,12 @@ public class MultiplayerServerController {

private readonly IMultiplayerServer server;
private readonly IMultiplayerClient client;
private readonly EventDispatcher events;

public MultiplayerServerController(IMultiplayerServer server, IMultiplayerClient client, EventDispatcher events) {
this.server = server;
this.client = client;
this.events = events;

events.Subscribe<GameStartedEvent>(OnGameStarted);
events.Subscribe<GameQuitEvent>(OnGameQuit);
Expand All @@ -32,7 +35,14 @@ private void OnGameStarted(GameStartedEvent @event) {
if (@event.Multiplayer.Mode != MultiplayerMode.Host)
return;

LoadOverlay.Show("Starting host...");
MultiplayerStatusOverlay.Show("Starting host...");
events.Subscribe<PlayersReadyEvent>(
(_, subscription) => {
MultiplayerStatusOverlay.Close();
subscription.Cancel();
}
);

server.Start();
}

Expand Down
2 changes: 2 additions & 0 deletions src/MultiplayerMod/Multiplayer/Players/MultiplayerPlayers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class MultiplayerPlayers : IEnumerable<MultiplayerPlayer> {

public IEnumerator<MultiplayerPlayer> GetEnumerator() => players.Values.GetEnumerator();

public int Count => players.Values.Count;

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public MultiplayerPlayer Current => players[currentPlayerId];
Expand Down
30 changes: 0 additions & 30 deletions src/MultiplayerMod/Multiplayer/UI/LoadOverlay.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using JetBrains.Annotations;
using MultiplayerMod.Core.Dependency;
using MultiplayerMod.Core.Events;
using MultiplayerMod.Core.Scheduling;
using MultiplayerMod.ModRuntime.StaticCompatibility;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace MultiplayerMod.Multiplayer.UI.Overlays;

public class MultiplayerStatusOverlay {

public static string Text {
get => overlay?.text ?? "";
set {
if (overlay == null)
return;

overlay.text = value;
overlay.textComponent.text = value;
}
}

private LocText textComponent = null!;
private string text = "";

[InjectDependency, UsedImplicitly]
private UnityTaskScheduler scheduler = null!;

[InjectDependency, UsedImplicitly]
private EventDispatcher events = null!;

private static MultiplayerStatusOverlay? overlay;

private MultiplayerStatusOverlay() {
SceneManager.sceneLoaded += OnPostLoadScene;
Dependencies.Get<IDependencyInjector>().Inject(this);
CreateOverlay();
}

private void CreateOverlay() {
LoadingOverlay.Load(() => { });
textComponent = LoadingOverlay.instance.GetComponentInChildren<LocText>();
textComponent.alignment = TextAlignmentOptions.Top;
textComponent.margin = new Vector4(0, -21.0f, 0, 0);
textComponent.text = text;

var rect = textComponent.gameObject.GetComponent<RectTransform>();
rect.sizeDelta = new Vector2(Screen.width, 0);

var scale = LoadingOverlay.instance.GetComponentInParent<KCanvasScaler>().GetCanvasScale();
ScreenResize.Instance.OnResize += () => rect.sizeDelta = new Vector2(Screen.width / scale, 0);
}

private void Dispose() {
SceneManager.sceneLoaded -= OnPostLoadScene;
LoadingOverlay.Clear();
}

private void OnPostLoadScene(Scene scene, LoadSceneMode mode) => scheduler.Run(CreateOverlay);

public static void Show(string text) {
overlay ??= new MultiplayerStatusOverlay();
Text = text;
}

public static void Close() {
overlay?.Dispose();
overlay = null;
}

}
57 changes: 50 additions & 7 deletions src/MultiplayerMod/Multiplayer/World/WorldManager.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using MultiplayerMod.Core.Dependency;
using MultiplayerMod.Core.Events;
using MultiplayerMod.Core.Extensions;
using MultiplayerMod.Core.Scheduling;
using MultiplayerMod.ModRuntime.Context;
using MultiplayerMod.ModRuntime.StaticCompatibility;
using MultiplayerMod.Multiplayer.Commands.Overlay;
using MultiplayerMod.Multiplayer.Commands.Speed;
using MultiplayerMod.Multiplayer.Commands.World;
using MultiplayerMod.Multiplayer.CoreOperations.PlayersManagement.Commands;
using MultiplayerMod.Multiplayer.Players;
using MultiplayerMod.Multiplayer.Players.Events;
using MultiplayerMod.Multiplayer.UI.Overlays;
using MultiplayerMod.Network;

namespace MultiplayerMod.Multiplayer.World;
Expand All @@ -18,34 +22,73 @@ public class WorldManager {

private readonly IMultiplayerServer server;
private readonly MultiplayerGame multiplayer;
private readonly EventDispatcher events;
private readonly UnityTaskScheduler scheduler;

public WorldManager(IMultiplayerServer server, MultiplayerGame multiplayer) {
public WorldManager(
IMultiplayerServer server,
MultiplayerGame multiplayer,
EventDispatcher events,
UnityTaskScheduler scheduler
) {
this.server = server;
this.multiplayer = multiplayer;
this.events = events;
this.scheduler = scheduler;
}

public void Sync() {
SetupStatusOverlay();

var resume = !SpeedControlScreen.Instance.IsPaused;
server.Send(new PauseGame());
multiplayer.Objects.SynchronizeWithTracker();
multiplayer.Players.ForEach(it => server.Send(new ChangePlayerStateCommand(it.Id, PlayerState.Loading)));
server.Send(new ChangePlayerStateCommand(multiplayer.Players.Current.Id, PlayerState.Ready));
server.Send(new ShowLoadOverlay());
server.Send(new LoadWorld(WorldName, GetWorldSave()), MultiplayerCommandOptions.SkipHost);

if (resume)
server.Send(new ResumeGame());
}

public static void LoadWorldSave(string worldName, byte[] data) {
private void SetupStatusOverlay() {
MultiplayerStatusOverlay.Show("Waiting for players...");
events.Subscribe<PlayerStateChangedEvent>(
(_, subscription) => {
var players = multiplayer.Players;
if (players.Ready) {
MultiplayerStatusOverlay.Close();
subscription.Cancel();
}
var readyPlayersCount = players.Count(it => it.State == PlayerState.Ready);
var playerList = string.Join("\n", players.Select(it => $"{it.Profile.PlayerName}: {it.State}"));
var statusText = $"Waiting for players ({readyPlayersCount}/{players.Count} ready)...\n{playerList}";
MultiplayerStatusOverlay.Text = statusText;
}
);
}

public void RequestWorldLoad(string name, byte[] data) {
MultiplayerStatusOverlay.Show($"Loading {name}...");
events.Subscribe<PlayersReadyEvent>(
(_, subscription) => {
MultiplayerStatusOverlay.Close();
subscription.Cancel();
}
);
scheduler.Run(() => LoadWorldSave(name, data));
}

private void LoadWorldSave(string name, byte[] data) {
var savePath = SaveLoader.GetCloudSavesDefault()
? SaveLoader.GetCloudSavePrefix()
: SaveLoader.GetSavePrefixAndCreateFolder();

var path = Path.Combine(savePath, worldName, $"{worldName}.sav");
var path = Path.Combine(savePath, name, $"{name}.sav");
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
using (var writer = new BinaryWriter(File.OpenWrite(path))) {
using (var writer = new BinaryWriter(File.OpenWrite(path)))
writer.Write(data);
}

LoadScreen.DoLoad(path);
}

Expand Down

0 comments on commit 477f296

Please sign in to comment.