Skip to content

Commit

Permalink
Merge branch 'pr-ux' into 0.8.10-test
Browse files Browse the repository at this point in the history
  • Loading branch information
starfi5h committed Jun 20, 2022
2 parents c69a12e + f816a37 commit 4664b49
Show file tree
Hide file tree
Showing 17 changed files with 143 additions and 31 deletions.
3 changes: 3 additions & 0 deletions NebulaModel/MultiplayerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ public bool StreamerMode {
[DisplayName("Default chat size"), Category("Chat")]
public ChatSize DefaultChatSize { get; set; } = ChatSize.Medium;

[DisplayName("Notification duration"), Category("Chat")]
public int NotificationDuration { get; set; } = 15;

[DisplayName("Cleanup inactive sessions"), Category("Network")]
[Description("If disabled the underlying networking library will not cleanup inactive connections. This might solve issues with clients randomly disconnecting and hosts having a 'System.ObjectDisposedException'.")]
public bool CleanupInactiveSessions { get; set; } = true;
Expand Down
13 changes: 13 additions & 0 deletions NebulaModel/Packets/GameStates/FragmentInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace NebulaModel.Packets.GameStates
{
public class FragmentInfo
{
public int Size { get; set; }

public FragmentInfo() { }
public FragmentInfo(int size)
{
Size = size;
}
}
}
31 changes: 28 additions & 3 deletions NebulaNetwork/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
using NebulaModel.Packets.Session;
using NebulaModel.Utils;
using NebulaWorld;
using NebulaWorld.SocialIntegration;
using NebulaWorld.GameStates;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
Expand All @@ -21,6 +22,7 @@ namespace NebulaNetwork
{
public class Client : NetworkProvider, IClient
{
private const float FRAGEMENT_UPDATE_INTERVAL = 0.1f;
private const float GAME_STATE_UPDATE_INTERVAL = 1f;
private const float MECHA_SYNCHONIZATION_INTERVAL = 30f;

Expand All @@ -32,7 +34,7 @@ public class Client : NetworkProvider, IClient
private NebulaConnection serverConnection;
private bool websocketAuthenticationFailure;


private float fragmentUpdateTimer = 0f;
private float mechaSynchonizationTimer = 0f;
private float gameStateUpdateTimer = 0f;

Expand Down Expand Up @@ -186,13 +188,23 @@ public override void Update()
gameStateUpdateTimer = 0f;
}
}

fragmentUpdateTimer += Time.deltaTime;
if (fragmentUpdateTimer >= FRAGEMENT_UPDATE_INTERVAL)
{
if (GameStatesManager.FragmentSize > 0)
{
GameStatesManager.UpdateBufferLength(GetFragmentBufferLength());
}
fragmentUpdateTimer = 0f;
}
}

private void ClientSocket_OnMessage(object sender, MessageEventArgs e)
{
if (!Multiplayer.IsLeavingGame)
{
PacketProcessor.EnqueuePacketForProcessing(e.RawData, new NebulaConnection(clientSocket, serverEndpoint, PacketProcessor));
PacketProcessor.EnqueuePacketForProcessing(e.RawData, serverConnection);
}
}

Expand Down Expand Up @@ -322,5 +334,18 @@ private static void DisableNagleAlgorithm(WebSocket socket)
tcpClient.NoDelay = true;
}
}

private int GetFragmentBufferLength()
{
MemoryStream buffer = (MemoryStream)AccessTools.Field(typeof(WebSocket), "_fragmentsBuffer").GetValue(clientSocket);
if (buffer != null)
{
return (int)buffer.Length;
}
else
{
return 0;
}
}
}
}
20 changes: 20 additions & 0 deletions NebulaNetwork/PacketProcessors/GameStates/FragmentInfoProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using NebulaAPI;
using NebulaModel.Networking;
using NebulaModel.Packets;
using NebulaModel.Packets.GameStates;
using NebulaWorld.GameStates;

namespace NebulaNetwork.PacketProcessors.GameStates
{
[RegisterPacketProcessor]
public class FragmentInfoProcessor : PacketProcessor<FragmentInfo>
{
public override void ProcessPacket(FragmentInfo packet, NebulaConnection conn)
{
if (IsClient)
{
GameStatesManager.FragmentSize = packet.Size;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ namespace NebulaNetwork.PacketProcessors.GameStates
[RegisterPacketProcessor]
public class GameStateUpdateProcessor : PacketProcessor<GameStateUpdate>
{
public float MaxUPS = 240f;
public float MinUPS = 30f;
public float BUFFERING_TIME = 30f;
public float BUFFERING_TICK = 60f;

Expand Down Expand Up @@ -68,23 +66,23 @@ public override void ProcessPacket(GameStateUpdate packet, NebulaConnection conn
// Adjust client's UPS to match game tick with server, range 30~120 UPS
float UPS = diff / 1f + avaerageUPS;
long skipTick = 0;
if (UPS > MaxUPS)
if (UPS > GameStatesManager.MaxUPS)
{
// Try to disturbute game tick difference into BUFFERING_TIME (seconds)
if (diff / BUFFERING_TIME + avaerageUPS > MaxUPS)
if (diff / BUFFERING_TIME + avaerageUPS > GameStatesManager.MaxUPS)
{
// The difference is too large, need to skip ticks to catch up
skipTick = (long)(UPS - MaxUPS);
skipTick = (long)(UPS - GameStatesManager.MaxUPS);
}
UPS = MaxUPS;
UPS = GameStatesManager.MaxUPS;
}
else if (UPS < MinUPS)
else if (UPS < GameStatesManager.MinUPS)
{
if (diff + avaerageUPS - MinUPS < -BUFFERING_TICK)
if (diff + avaerageUPS - GameStatesManager.MinUPS < -BUFFERING_TICK)
{
skipTick = (long)(UPS - MinUPS);
skipTick = (long)(UPS - GameStatesManager.MinUPS);
}
UPS = MinUPS;
UPS = GameStatesManager.MinUPS;
}
if (skipTick != 0)
{
Expand Down
4 changes: 4 additions & 0 deletions NebulaNetwork/PacketProcessors/Planet/FactoryDataProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using NebulaModel.Packets;
using NebulaModel.Packets.Planet;
using NebulaWorld;
using NebulaWorld.GameStates;

namespace NebulaNetwork.PacketProcessors.Planet
{
Expand All @@ -16,6 +17,9 @@ public override void ProcessPacket(FactoryData packet, NebulaConnection conn)
{
return;
}
// The whole fragment is received
GameStatesManager.FragmentSize = 0;

// Stop packet processing until factory is imported and loaded
((NebulaModel.NetworkProvider)Multiplayer.Session.Network).PacketProcessor.Enable = false;
Log.Info($"Pause PacketProcessor (FactoryDataProcessor)");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NebulaModel.Networking;
using NebulaModel.Packets;
using NebulaModel.Packets.Planet;
using NebulaModel.Packets.GameStates;
using NebulaWorld;

namespace NebulaNetwork.PacketProcessors.Planet
Expand All @@ -25,6 +26,7 @@ public override void ProcessPacket(FactoryLoadRequest packet, NebulaConnection c
factory.Export(writer.BinaryWriter);
byte[] data = writer.CloseAndGetBytes();
Log.Info($"Sent {data.Length} bytes of data for PlanetFactory {planet.name} (ID: {planet.id})");
conn.SendPacket(new FragmentInfo(data.Length + planet.data.modData.Length));
conn.SendPacket(new FactoryData(packet.PlanetID, data, planet.data.modData));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using NebulaModel.Packets;
using NebulaModel.Packets.Universe;
using NebulaWorld;
using NebulaWorld.GameStates;
using System.Collections.Generic;
using System.IO;

Expand Down Expand Up @@ -41,6 +42,8 @@ public override void ProcessPacket(DysonSphereData packet, NebulaConnection conn
break;

case DysonSphereRespondEvent.Load:
// The whole fragment is received
GameStatesManager.FragmentSize = 0;
//Failsafe, if client does not have instantiated sphere for the star, it will create dummy one that will be replaced during import
GameMain.data.dysonSpheres[packet.StarIndex] = new DysonSphere();
GameMain.data.statistics.production.Init(GameMain.data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using NebulaModel.Logger;
using NebulaModel.Networking;
using NebulaModel.Packets;
using NebulaModel.Packets.GameStates;
using NebulaModel.Packets.Universe;
using NebulaWorld;
using System.Collections.Generic;
Expand Down Expand Up @@ -45,6 +46,7 @@ public override void ProcessPacket(DysonSphereLoadRequest packet, NebulaConnecti
dysonSphere.Export(writer.BinaryWriter);
byte[] data = writer.CloseAndGetBytes();
Log.Info($"Sent {data.Length} bytes of data for DysonSphereData (INDEX: {packet.StarIndex})");
conn.SendPacket(new FragmentInfo(data.Length));
conn.SendPacket(new DysonSphereData(packet.StarIndex, data, DysonSphereRespondEvent.Load));
Multiplayer.Session.DysonSpheres.RegisterPlayer(conn, packet.StarIndex);
}
Expand Down
38 changes: 27 additions & 11 deletions NebulaNetwork/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
using NebulaAPI;
using NebulaModel;
using NebulaModel.DataStructures;
using NebulaModel.Logger;
using NebulaModel.Networking;
using NebulaModel.Networking.Serialization;
using NebulaModel.Packets.GameHistory;
using NebulaModel.Packets.GameStates;
using NebulaModel.Packets.Universe;
using NebulaModel.Utils;
using NebulaWorld;
using Open.Nat;
using NebulaWorld.SocialIntegration;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Reflection;
using System.Threading.Tasks;
Expand Down Expand Up @@ -84,15 +85,15 @@ public override void Start()
{
var device = await discoverer.DiscoverDeviceAsync();
await device.CreatePortMapAsync(new Mapping(Protocol.Tcp, port, port, "DSP nebula"));
NebulaModel.Logger.Log.Info($"Successfully created UPnp or Pmp port mapping for {port}");
Log.Info($"Successfully created UPnp or Pmp port mapping for {port}");
}
catch (NatDeviceNotFoundException)
{
NebulaModel.Logger.Log.WarnInform("No UPnp or Pmp compatible/enabled NAT device found");
Log.WarnInform("No UPnp or Pmp compatible/enabled NAT device found");
}
catch (MappingException)
{
NebulaModel.Logger.Log.WarnInform("Could not create UPnp or Pmp port mapping");
Log.WarnInform("Could not create UPnp or Pmp port mapping");
}
});
}
Expand All @@ -101,7 +102,7 @@ public override void Start()

socket = new WebSocketServer(System.Net.IPAddress.IPv6Any, port);
socket.Log.Level = LogLevel.Debug;
socket.Log.Output = NebulaModel.Logger.Log.SocketOutput;
socket.Log.Output = Log.SocketOutput;
socket.AllowForwardedRequest = true; // This is required to make the websocket play nice with tunneling services like ngrok

if (!string.IsNullOrWhiteSpace(Config.Options.ServerPassword))
Expand Down Expand Up @@ -149,7 +150,7 @@ public override void Start()
DiscordManager.UpdateRichPresence(ip: ip, updateTimestamp: true);
if (Multiplayer.IsDedicated)
{
NebulaModel.Logger.Log.Info($">> Ngrok address: {ip}");
Log.Info($">> Ngrok address: {ip}");
}
}
else
Expand Down Expand Up @@ -271,13 +272,15 @@ private class WebSocketService : WebSocketBehavior
{
public static IPlayerManager PlayerManager;
public static NetPacketProcessor PacketProcessor;
public static Dictionary<int, NebulaConnection> ConnectionDictonary = new();

public WebSocketService() { }

public WebSocketService(IPlayerManager playerManager, NetPacketProcessor packetProcessor)
{
PlayerManager = playerManager;
PacketProcessor = packetProcessor;
ConnectionDictonary.Clear();
}

protected override void OnOpen()
Expand All @@ -289,18 +292,29 @@ protected override void OnOpen()
return;
}

NebulaModel.Logger.Log.Info($"Client connected ID: {ID}");
Log.Info($"Client connected ID: {ID}");
NebulaConnection conn = new NebulaConnection(Context.WebSocket, Context.UserEndPoint, PacketProcessor);
PlayerManager.PlayerConnected(conn);
ConnectionDictonary.Add(Context.UserEndPoint.GetHashCode(), conn);
}

protected override void OnMessage(MessageEventArgs e)
{
PacketProcessor.EnqueuePacketForProcessing(e.RawData, new NebulaConnection(Context.WebSocket, Context.UserEndPoint, PacketProcessor));
// Find created NebulaConnection
if (ConnectionDictonary.TryGetValue(Context.UserEndPoint.GetHashCode(), out NebulaConnection conn))
{
PacketProcessor.EnqueuePacketForProcessing(e.RawData, conn);
}
else
{
Log.Warn($"Unregister socket {Context.UserEndPoint.GetHashCode()}");
}
}

protected override void OnClose(CloseEventArgs e)
{
ConnectionDictonary.Remove(Context.UserEndPoint.GetHashCode());

// If the reason of a client disconnect is because we are still loading the game,
// we don't need to inform the other clients since the disconnected client never
// joined the game in the first place.
Expand All @@ -309,7 +323,7 @@ protected override void OnClose(CloseEventArgs e)
return;
}

NebulaModel.Logger.Log.Info($"Client disconnected: {ID}, reason: {e.Reason}");
Log.Info($"Client disconnected: {ID}, reason: {e.Reason}");
UnityDispatchQueue.RunOnMainThread(() =>
{
// This is to make sure that we don't try to deal with player disconnection
Expand All @@ -323,8 +337,10 @@ protected override void OnClose(CloseEventArgs e)

protected override void OnError(ErrorEventArgs e)
{
ConnectionDictonary.Remove(Context.UserEndPoint.GetHashCode());

// TODO: seems like clients erroring out in the sync process can lock the host with the joining player message, maybe this fixes it
NebulaModel.Logger.Log.Info($"Client disconnected because of an error: {ID}, reason: {e.Exception}");
Log.Info($"Client disconnected because of an error: {ID}, reason: {e.Exception}");
UnityDispatchQueue.RunOnMainThread(() =>
{
// This is to make sure that we don't try to deal with player disconnection
Expand Down
3 changes: 3 additions & 0 deletions NebulaPatcher/NebulaPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ private static void InitPatches()
catch (Exception ex)
{
Log.Error("Unhandled exception occurred while patching the game:", ex);
// Show error in UIFatalErrorTip to inform normal users
Harmony.CreateAndPatchAll(typeof(UIFatalErrorTip_Patch));
Log.Error($"Nebula Multiplayer Mod is incompatible\nUnhandled exception occurred while patching the game.");
}
}

Expand Down
5 changes: 3 additions & 2 deletions NebulaPatcher/Patches/Dynamic/GameLoader_Patch.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using HarmonyLib;
using NebulaModel.Logger;
using NebulaWorld;
using NebulaWorld.GameStates;

namespace NebulaPatcher.Patches.Dynamic
{
Expand All @@ -11,7 +11,8 @@ internal class GameLoader_Patch
[HarmonyPatch(nameof(GameLoader.FixedUpdate))]
public static void FixedUpdate_Postfix(int ___frame)
{
InGamePopup.UpdateMessage("Loading", "Loading state from server, please wait\n" + Log.LastInfoMsg);
string content = GameStatesManager.FragmentSize > 0 ? GameStatesManager.LoadingMessage() : NebulaModel.Logger.Log.LastInfoMsg;
InGamePopup.UpdateMessage("Loading", "Loading state from server, please wait\n" + content);
if (Multiplayer.IsActive && ___frame >= 11)
{
Multiplayer.Session.OnGameLoadCompleted();
Expand Down
2 changes: 2 additions & 0 deletions NebulaPatcher/Patches/Dynamic/UIFatalErrorTip_Patch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public static void _OnOpen_Postfix(UIFatalErrorTip __instance)
{
button = GameObject.Find("UI Root/Overlay Canvas/In Game/Windows/Dyson Sphere Editor/Dyson Editor Control Panel/hierarchy/layers/blueprint-group/blueprint-2/copy-button");
GameObject errorPanel = GameObject.Find("UI Root/Overlay Canvas/Fatal Error/errored-panel/");
errorPanel.transform.Find("tip-text-0").GetComponent<Text>().text = Title();
GameObject.Destroy(errorPanel.transform.Find("tip-text-0").GetComponent<Localizer>());
errorPanel.transform.Find("tip-text-1").GetComponent<Text>().text = Title();
GameObject.Destroy(errorPanel.transform.Find("tip-text-1").GetComponent<Localizer>());
button = GameObject.Instantiate(button, errorPanel.transform);
Expand Down
2 changes: 1 addition & 1 deletion NebulaWorld/Factory/BuildToolManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public void CreatePrebuildsRequest(CreatePrebuildsRequest packet)
if (packet.PrebuildId != Multiplayer.Session.Factories.GetNextPrebuildId(packet.PlanetId))
{
string warningText = $"PrebuildId mismatch on {packet.PlanetId} planet: {packet.PrebuildId} != {Multiplayer.Session.Factories.GetNextPrebuildId(planet.factory)}";
Log.Warn(warningText);
Log.WarnInform(warningText + ". Consider reconnecting");
NebulaWorld.Warning.WarningManager.DisplayTemporaryWarning(warningText, 5000);
}
}
Expand Down
Loading

0 comments on commit 4664b49

Please sign in to comment.