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

Automatic entity resync #2145

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NitroxPatcher.PatternMatching;
using NitroxTest.Patcher;

namespace NitroxPatcher.Patches.Dynamic;
Expand All @@ -12,19 +10,9 @@ public class Inventory_LoseItems_PatchTest
[TestMethod]
public void Sanity()
{
List<CodeInstruction> instructions = PatchTestHelper.GenerateDummyInstructions(100);
instructions.Add(new CodeInstruction(Inventory_LoseItems_Patch.INJECTION_OPCODE, Inventory_LoseItems_Patch.INJECTION_OPERAND));

IEnumerable<CodeInstruction> result = Inventory_LoseItems_Patch.Transpiler(null, instructions);
Assert.AreEqual(instructions.Count, result.Count());
}

[TestMethod]
public void InjectionSanity()
{
IEnumerable<CodeInstruction> beforeInstructions = PatchTestHelper.GetInstructionsFromMethod(Inventory_LoseItems_Patch.TARGET_METHOD);
IEnumerable<CodeInstruction> result = Inventory_LoseItems_Patch.Transpiler(Inventory_LoseItems_Patch.TARGET_METHOD, beforeInstructions);

Assert.IsTrue(beforeInstructions.Count() > result.Count());
IEnumerable<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(Inventory_LoseItems_Patch.TARGET_METHOD);
IEnumerable<CodeInstruction> transformedIl = Inventory_LoseItems_Patch.Transpiler(originalIl);
Console.WriteLine(transformedIl.ToPrettyString());
transformedIl.Count().Should().Be(originalIl.Count() + 5);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,19 @@ public LiteNetLibClient(PacketReceiver packetReceiver, INetworkDebugger networkD
client = new NetManager(listener)
{
UpdateTime = 15,
EnableStatistics = true,
DisconnectOnUnreachable = true,
#if DEBUG
DisconnectTimeout = 300000 //Disables Timeout (for 5 min) for debug purpose (like if you jump though the server code)
#endif
};
}

public NetStatistics GetStatistics()
{
return client.Statistics;
}

public async Task StartAsync(string ipAddress, int serverPort)
{
Log.Info("Initializing LiteNetLibClient...");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public override void Process(EntityReparented packet)
// In some cases, the affected entity may be pending spawning or out of range.
// we only require the parent (in this case, the visible entity is undergoing
// some change that must be shown, and if not is an error).
entities.RequireResync(packet.Id);
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
Expand All @@ -14,10 +14,12 @@ public class PlayerHeldItemChangedProcessor : ClientPacketProcessor<PlayerHeldIt
private int defaultLayer;
private int viewModelLayer;
private readonly PlayerManager playerManager;
private readonly Entities entities;

public PlayerHeldItemChangedProcessor(PlayerManager playerManager)
public PlayerHeldItemChangedProcessor(PlayerManager playerManager, Entities entities)
{
this.playerManager = playerManager;
this.entities = entities;

if (NitroxEnvironment.IsNormal)
{
Expand All @@ -38,6 +40,7 @@ public override void Process(PlayerHeldItemChanged packet)

if (!NitroxEntity.TryGetObjectFrom(packet.ItemId, out GameObject item))
{
entities.RequireResync(packet.ItemId);
return; // Item can be not spawned yet bc async.
}

Expand Down
5 changes: 4 additions & 1 deletion NitroxClient/GameLogic/Entities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using NitroxClient.Communication;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract;
using NitroxClient.GameLogic.Settings;
using NitroxClient.GameLogic.Spawning;
using NitroxClient.GameLogic.Spawning.Abstract;
using NitroxClient.GameLogic.Spawning.Bases;
Expand All @@ -24,7 +25,7 @@

namespace NitroxClient.GameLogic
{
public class Entities
public partial class Entities
{
private readonly IPacketSender packetSender;
private readonly ThrottledPacketSender throttledPacketSender;
Expand Down Expand Up @@ -133,6 +134,7 @@ private IEnumerator SpawnNewEntities()
{
entityMetadataManager.ClearNewerMetadata();
deletedEntitiesIds.Clear();
RequestEntityResyncs();
}
}

Expand Down Expand Up @@ -320,6 +322,7 @@ public bool WasParentSpawned(NitroxId id)
public void MarkAsSpawned(Entity entity)
{
spawnedAsType[entity.Id] = entity.GetType();
requiredEntityResyncs.Remove(entity.Id);
}

public void RemoveEntity(NitroxId id) => spawnedAsType.Remove(id);
Expand Down
33 changes: 33 additions & 0 deletions NitroxClient/GameLogic/EntityResyncer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.Generic;
using NitroxClient.GameLogic.Settings;
using NitroxModel.DataStructures;
using NitroxModel.Packets;

namespace NitroxClient.GameLogic;

public partial class Entities
{
private readonly HashSet<NitroxId> requiredEntityResyncs = [];

public int Requests;

public void RequireResync(NitroxId nitroxId)
{
if (NitroxPrefs.EntitiesAutoResync.Value)
{
requiredEntityResyncs.Add(nitroxId);
RequestEntityResyncs();
}
}

public void RequestEntityResyncs()
{
if (spawningEntities || !NitroxPrefs.EntitiesAutoResync.Value || requiredEntityResyncs.Count == 0)
{
return;
}
Requests += requiredEntityResyncs.Count;
packetSender.Send(new EntityResyncRequest([.. requiredEntityResyncs]));
requiredEntityResyncs.Clear();
}
}
1 change: 1 addition & 0 deletions NitroxClient/GameLogic/Settings/NitroxPrefs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class NitroxPrefs
public static readonly NitroxPref<bool> ChatUsed = new("Nitrox.chatUsed");
public static readonly NitroxPref<bool> SafeBuilding = new("Nitrox.safeBuilding", true);
public static readonly NitroxPref<bool> SafeBuildingLog = new("Nitrox.safeBuildingLog", true);
public static readonly NitroxPref<bool> EntitiesAutoResync = new("Nitrox.entitiesAutoResync", true);
}

public abstract class NitroxPref { }
Expand Down
14 changes: 14 additions & 0 deletions NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using NitroxClient.GameLogic.Bases;
using NitroxClient.MonoBehaviours;
using NitroxClient.MonoBehaviours.Gui.MainMenu;
using NitroxModel.Core;
using UnityEngine.Events;

namespace NitroxClient.GameLogic.Settings;

public class NitroxSettingsManager
{
private readonly Lazy<Vehicles> vehicles = new(NitroxServiceLocator.LocateService<Vehicles>);
/// <summary>
/// Settings grouped by their headings
/// </summary>
Expand Down Expand Up @@ -49,6 +52,17 @@ private void MakeSettings()
}
}));

AddSetting("Nitrox_ResyncSettings", new Setting("Nitrox_ResyncVehicles", () =>
{
if (Multiplayer.Main && Multiplayer.Main.InitialSyncCompleted)
{
vehicles.Value.AskForResync();
}
}));

AddSetting("Nitrox_ResyncSettings", new Setting("Nitrox_EntitiesAutoResync", NitroxPrefs.EntitiesAutoResync, autoResync => NitroxPrefs.EntitiesAutoResync.Value = autoResync));


AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuilding", NitroxPrefs.SafeBuilding, safe => NitroxPrefs.SafeBuilding.Value = safe));
AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuildingLog", NitroxPrefs.SafeBuildingLog, safeLog => NitroxPrefs.SafeBuildingLog.Value = safeLog));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections;
using System.Linq;
using NitroxClient.MonoBehaviours;
using NitroxClient.MonoBehaviours.CinematicController;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.DataStructures.Util;
using NitroxModel.Helper;
Expand Down Expand Up @@ -46,7 +48,9 @@ public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, E
}
}

yield return SpawnInWorld(vehicleEntity, result, parent);
yield return SpawnInWorld(vehicleEntity, result, parent);

yield return entities.SpawnBatchAsync(entity.ChildEntities);
}

private IEnumerator SpawnInWorld(VehicleWorldEntity vehicleEntity, TaskResult<Optional<GameObject>> result, Optional<GameObject> parent)
Expand Down Expand Up @@ -192,7 +196,7 @@ private void DockVehicle(GameObject gameObject, GameObject parent)

public bool SpawnsOwnChildren()
{
return false;
return true;
}

private float GetCraftDuration(TechType techType)
Expand Down
23 changes: 23 additions & 0 deletions NitroxClient/GameLogic/Vehicles.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections;
using NitroxClient.Communication;
using NitroxClient.Communication.Abstract;
Expand All @@ -17,6 +18,9 @@ namespace NitroxClient.GameLogic;

public class Vehicles
{
private static readonly TimeSpan ResyncRequestCooldown = TimeSpan.FromSeconds(10);
private DateTimeOffset LatestResyncRequestTimeOffset;

private readonly IPacketSender packetSender;
private readonly IMultiplayerSession multiplayerSession;

Expand Down Expand Up @@ -241,4 +245,23 @@ public static VehicleWorldEntity BuildVehicleWorldEntity(GameObject constructedO
VehicleChildEntityHelper.PopulateChildren(constructedObjectId, constructedObject.GetFullHierarchyPath(), vehicleEntity.ChildEntities, constructedObject);
return vehicleEntity;
}

public void AskForResync()
{
if (!Multiplayer.Main || !Multiplayer.Main.InitialSyncCompleted)
{
return;
}
TimeSpan deltaTime = DateTimeOffset.UtcNow - LatestResyncRequestTimeOffset;
if (deltaTime < ResyncRequestCooldown)
{
double timeLeft = ResyncRequestCooldown.TotalSeconds - deltaTime.TotalSeconds;
Log.InGame(Language.main.Get("Nitrox_ResyncOnCooldown").Replace("{TIME_LEFT}", string.Format("{0:N2}", timeLeft)));
return;
}
LatestResyncRequestTimeOffset = DateTimeOffset.UtcNow;

packetSender.Send(new VehiclesResyncRequest());
Log.InGame(Language.main.Get("Nitrox_ResyncRequestedVehicles"));
}
}
16 changes: 16 additions & 0 deletions NitroxModel/Packets/EntityResyncRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using NitroxModel.DataStructures;

namespace NitroxModel.Packets;

[Serializable]
public sealed class EntityResyncRequest : Packet
{
public List<NitroxId> RequestedIds { get; }

public EntityResyncRequest(List<NitroxId> requestedIds)
{
RequestedIds = requestedIds;
}
}
17 changes: 17 additions & 0 deletions NitroxModel/Packets/VehiclesResyncRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using NitroxModel.DataStructures;

namespace NitroxModel.Packets;

[Serializable]
public sealed class VehiclesResyncRequest : Packet
{
public NitroxId EntityId { get; }
public bool ResyncEverything { get; }

public VehiclesResyncRequest(NitroxId entityId = null, bool resyncEverything = true)
{
EntityId = entityId;
ResyncEverything = resyncEverything;
}
}
11 changes: 11 additions & 0 deletions NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#if DEBUG
using System.Reflection;
using LiteNetLib;
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.NetworkingLayer.LiteNetLib;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.Helper;
Expand All @@ -10,6 +13,8 @@ public sealed partial class FPSCounter_UpdateDisplay_Patch : NitroxPatch, IDynam
{
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((FPSCounter t) => t.UpdateDisplay());

private static LiteNetLibClient client => Resolve<IClient>() as LiteNetLibClient;

public static void Postfix(FPSCounter __instance)
{
if (!Multiplayer.Active)
Expand All @@ -18,6 +23,12 @@ public static void Postfix(FPSCounter __instance)
}
__instance.strBuffer.Append("Loading entities: ").AppendLine(Resolve<Entities>().EntitiesToSpawn.Count.ToString());
__instance.strBuffer.Append("Real time elapsed: ").AppendLine(Resolve<TimeManager>().RealTimeElapsed.ToString());
__instance.strBuffer.Append("Requested entity resyncs: ").AppendLine(Resolve<Entities>().Requests.ToString());
if (client.IsConnected)
{
NetStatistics stats = client.GetStatistics();
__instance.strBuffer.Append("Packet Loss: ").AppendLine($"{stats.PacketLoss} [{stats.PacketLossPercent}%]");
}
__instance.text.SetText(__instance.strBuffer);
}
}
Expand Down
Loading