Skip to content

Commit

Permalink
Merge pull request NebulaModTeam#645 from NebulaModTeam/0.10.x-networ…
Browse files Browse the repository at this point in the history
…king-refactor

API 2.0 networking refactor
  • Loading branch information
starfi5h authored Jan 29, 2024
2 parents ba4da86 + 0ae5cbf commit 9609c2b
Show file tree
Hide file tree
Showing 87 changed files with 1,030 additions and 1,303 deletions.
1 change: 0 additions & 1 deletion .github/scripts/thunderstore_bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ function generateManifest() {
BEPINEX_DEPENDENCY,
`nebula-${apiPluginInfo.name}-${apiPluginInfo.version}`,
"PhantomGamers-IlLine-1.0.0",
"CommonAPI-CommonAPI-1.5.7",
"starfi5h-BulletTime-1.2.10",
],
website_url: "https://github.com/hubastard/nebula"
Expand Down
3 changes: 0 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@
<PackageReference Include="BepInEx.Core" Version="5.*" PrivateAssets="all"/>
<PackageReference Include="UnityEngine.Modules" Version="2018.4.12" IncludeAssets="compile" PrivateAssets="all"/>
<PackageReference Include="DysonSphereProgram.GameLibs" Version="*-*" IncludeAssets="compile" PrivateAssets="all"/>
<PackageReference Include="DysonSphereProgram.Modding.CommonAPI" Version="1.5.7" IncludeAssets="compile" PrivateAssets="all"
Condition=" '$(MSBuildProjectName)' != 'NebulaAPI' "/>

</ItemGroup>

<ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">
Expand Down
13 changes: 13 additions & 0 deletions NebulaAPI/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
## Changelog

2.0.0:

- Remove `IPlayerManager`. Use the new class `ConcurrentPlayerCollection` instead.
- Add `IsDedicated`, `IsClient`, `IsServer` properties in `IMultiplayerSession`
- Add `SendToMatching` method in `INetworkProvider`
- Add `ConstructionModule`, `FightData`, `UpdateMech` in `IMechaData`
- Add combat-related upgrade settings in `IPlayerTechBonuses`

<details>
<summary>Previous Changelog</summary>

1.3.1:

- Added DeliveryPackage to IMechaData and IPlayerTechBonuses
Expand Down Expand Up @@ -42,3 +53,5 @@
1.0.0:

- initial release on thunderstore

</details>
160 changes: 160 additions & 0 deletions NebulaAPI/DataStructures/ConcurrentPlayerCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using NebulaAPI.GameState;
using NebulaAPI.Networking;

namespace NebulaAPI.DataStructures;

public class ConcurrentPlayerCollection
{
/// <summary>
/// Get a key value pair collection of pending players.
/// </summary>
/// <returns></returns>
public IReadOnlyDictionary<INebulaConnection, INebulaPlayer> Pending => playerCollections[EConnectionStatus.Pending];


/// <summary>
/// Get a key value pair collection of syncing players.
/// </summary>
/// <returns></returns>
public IReadOnlyDictionary<INebulaConnection, INebulaPlayer> Syncing => playerCollections[EConnectionStatus.Syncing];


/// <summary>
/// Get a key value pair collection of connected players.
/// </summary>
/// <returns></returns>
public IReadOnlyDictionary<INebulaConnection, INebulaPlayer> Connected => playerCollections[EConnectionStatus.Connected];

/// <summary>
/// Attempts to add a player to the collection.
/// </summary>
/// <param name="conn"></param>
/// <param name="newPlayer"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">True if successful, false if the player already exists.</exception>
public bool TryAdd(INebulaConnection conn, INebulaPlayer newPlayer)
{
if (conn.ConnectionStatus == EConnectionStatus.Undefined)
throw new InvalidOperationException("Could not add a player of undefined connection status.");
return playerCollections[conn.ConnectionStatus].TryAdd(conn, newPlayer);
}

/// <summary>
/// Attempts to remove a player from the collection.
/// </summary>
/// <param name="conn"></param>
/// <param name="removedPlayer"></param>
/// <returns>True if the player was removed.</returns>
public bool TryRemove(INebulaConnection conn, out INebulaPlayer removedPlayer)
{
return playerCollections[conn.ConnectionStatus].TryRemove(conn, out removedPlayer);
}

/// <summary>
/// Upgrades a player's connection status.
/// </summary>
/// <param name="player"></param>
/// <param name="newStatus"></param>
/// <returns>
/// True on success or if the player's status is already upgraded.
/// False if the player doesn't exist.
/// </returns>
public bool TryUpgrade(INebulaPlayer player, EConnectionStatus newStatus)
{
if (!playerCollections[player.Connection.ConnectionStatus].TryRemove(player.Connection, out _))
return false;
if (!playerCollections[newStatus].TryAdd(player.Connection, player))
return true;
player.Connection.ConnectionStatus = newStatus;
return true;
}

/// <summary>
/// Retrieves a player from the players list by their connection handle.
/// </summary>
/// <param name="conn"></param>
/// <param name="connectionStatus">The connection status cache to query</param>
/// <returns>
/// Player or null if the player has disconnected.
/// </returns>
public INebulaPlayer Get(INebulaConnection conn, EConnectionStatus connectionStatus = EConnectionStatus.Connected)
{
playerCollections[connectionStatus].TryGetValue(conn, out var player);
return player;
}

/// <summary>
/// Retrieves a player from the connected players list by their username.
/// </summary>
/// <param name="username"></param>
/// <param name="connectionStatus">The connection status cache to query</param>
/// <returns>
/// Player or null if the player has disconnected.
/// </returns>
public INebulaPlayer Get(string username, EConnectionStatus connectionStatus = EConnectionStatus.Connected)
{
var connectedPlayers = playerCollections[connectionStatus];
foreach (var kvp in connectedPlayers)
{
if (kvp.Value.Data.Username == username)
return kvp.Value;
}

return null;
}

/// <summary>
/// Retrieves a player from the connected players list by their player id.
/// </summary>
/// <param name="playerId"></param>
/// <param name="connectionStatus">The connection status cache to query</param>
/// <returns>
/// Player or null if the player has disconnected.
/// </returns>
public INebulaPlayer Get(ushort playerId, EConnectionStatus connectionStatus = EConnectionStatus.Connected)
{
var connectedPlayers = playerCollections[connectionStatus];
foreach (var kvp in connectedPlayers)
{
if (kvp.Value.Id == playerId)
return kvp.Value;
}

return null;
}

/// <summary>
/// Returns a collection of all player data, including host.
/// </summary>
/// <returns></returns>
public IEnumerable<IPlayerData> GetAllPlayerData()
{
var saves = playerCollections[EConnectionStatus.Connected]
.Select(p => p.Value.Data).ToList();
// If the host is a player, append their data to the list
if (!NebulaModAPI.MultiplayerSession.IsDedicated)
saves.Add(NebulaModAPI.MultiplayerSession.LocalPlayer.Data);

return saves;
}

private class ReducedConcurrentDictionary<TKey, TValue> : ConcurrentDictionary<TKey, TValue>
{
#pragma warning disable CA1822
// These are disabled as they create a new snapshot copy of the existing values.
public new ICollection<TKey> Keys => throw new InvalidOperationException("Accessing keys directly is not allowed.");
public new ICollection<TValue> Values => throw new InvalidOperationException("Accessing keys directly is not allowed.");
#pragma warning restore CA1822
}

private readonly Dictionary<EConnectionStatus, ReducedConcurrentDictionary<INebulaConnection, INebulaPlayer>> playerCollections = new()
{
{ EConnectionStatus.Pending, new ReducedConcurrentDictionary<INebulaConnection, INebulaPlayer>() },
{ EConnectionStatus.Syncing, new ReducedConcurrentDictionary<INebulaConnection, INebulaPlayer>() },
{ EConnectionStatus.Connected, new ReducedConcurrentDictionary<INebulaConnection, INebulaPlayer>() },
};
}
1 change: 0 additions & 1 deletion NebulaAPI/DataStructures/IMechaFightData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ namespace NebulaAPI.DataStructures
{
public interface IMechaFightData
{
// TODO: what about spaceSector? Will it be synced in dynamically? Or should it be stored?
bool AutoReplenishFuel { get; set; }
bool AutoReplenishAmmo { get; set; }
bool AutoReplenishHangar { get; set; }
Expand Down
4 changes: 3 additions & 1 deletion NebulaAPI/GameState/IMultiplayerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ public interface IMultiplayerSession
INetworkProvider Network { get; set; }
ILocalPlayer LocalPlayer { get; set; }
IFactoryManager Factories { get; set; }

bool IsDedicated { get; }
bool IsServer { get; }
bool IsClient { get; }
bool IsGameLoaded { get; set; }
}
2 changes: 1 addition & 1 deletion NebulaAPI/GameState/INebulaPlayer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#region

using NebulaAPI.Packets;
using NebulaAPI.Networking;

#endregion

Expand Down
17 changes: 13 additions & 4 deletions NebulaAPI/GameState/INetworkProvider.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
#region

using System;
using NebulaAPI.Packets;
using NebulaAPI.Networking;

#endregion

namespace NebulaAPI.GameState;

public interface INetworkProvider : IDisposable
{
IPlayerManager PlayerManager { get; set; }
/// <summary>
/// (intneral use)
/// </summary>
INetPacketProcessor PacketProcessor { get; }

/// <summary>
/// Send packet to Host (If ran on Client) or all Clients (If ran on Host)
/// </summary>
void SendPacket<T>(T packet) where T : class, new();

/// <summary>
/// Send a packet to all players that match a predicate
/// </summary>
/// <param name="packet"></param>
/// <param name="condition"></param>
/// <typeparam name="T"></typeparam>
public void SendToMatching<T>(T packet, Predicate<INebulaPlayer> condition) where T : class, new();

/// <summary>
/// Broadcast packet to all Players within current star system
/// </summary>
Expand Down Expand Up @@ -45,6 +56,4 @@ public interface INetworkProvider : IDisposable
/// Send packet to all Clients within star system except the excluded client
/// </summary>
void SendPacketToStarExclude<T>(T packet, int starId, INebulaConnection exclude) where T : class, new();

void Update();
}
60 changes: 0 additions & 60 deletions NebulaAPI/GameState/IPlayerManager.cs

This file was deleted.

Loading

0 comments on commit 9609c2b

Please sign in to comment.