Further parse bunches till their channel receives them

This commit is contained in:
AeonLucid
2022-01-10 20:20:02 +01:00
parent f3b0a31761
commit 0a27bdd3c0
23 changed files with 1187 additions and 51 deletions
@@ -0,0 +1,6 @@
namespace Prospect.Unreal.Core.Names;
public class UnrealConstants
{
public const int IndexNone = -1;
}
@@ -1,13 +1,11 @@
namespace Prospect.Unreal.Net.Channels; namespace Prospect.Unreal.Net.Channels;
public static class EChannelType public enum EChannelType
{ {
public const int CHTYPE_None = 0; // Invalid type. CHTYPE_None = 0, // Invalid type.
public const int CHTYPE_Control = 1; // Connection control. CHTYPE_Control = 1, // Connection control.
public const int CHTYPE_Actor = 2; // Actor-update channel. CHTYPE_Actor = 2, // Actor-update channel.
CHTYPE_File = 3, // Binary file transfer.
public const int CHTYPE_File = 3; // Binary file transfer. CHTYPE_Voice = 4, // VoIP data channel
CHTYPE_MAX = 8, // Maximum.
public const int CHTYPE_Voice = 4; // VoIP data channel
public const int CHTYPE_MAX = 8; // Maximum.
} }
@@ -0,0 +1,21 @@
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Net.Packets.Bunch;
namespace Prospect.Unreal.Net.Channels;
public class UActorChannel : UChannel
{
public UActorChannel()
{
ChType = EChannelType.CHTYPE_Actor;
ChName = UnrealNames.FNames[UnrealNameKey.Actor];
// bClearRecentActorRefs = true;
// bHoldQueuedExportBunchesAndGUIDs = false;
// QueuedCloseReason = EChannelCloseReason::Destroyed;
}
protected override void ReceivedBunch(FInBunch bunch)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,513 @@
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Exceptions;
using Prospect.Unreal.Net.Packets.Bunch;
using Serilog;
namespace Prospect.Unreal.Net.Channels;
public abstract class UChannel
{
private static readonly ILogger Logger = Log.ForContext<UChannel>();
private const int NetMaxConstructedPartialBunchSizeBytes = 1024 * 64;
/// <summary>
/// Owner connection.
/// </summary>
public UNetConnection Connection { get; set; }
/// <summary>
/// If OpenedLocally is true, this means we have acknowledged the packet we sent the bOpen bunch on.
/// Otherwise, it means we have received the bOpen bunch from the server.
/// </summary>
public bool OpenAcked { get; set; }
/// <summary>
/// State of the channel.
/// </summary>
public bool Closing { get; set; }
/// <summary>
/// Channel is going dormant (it will close but the client will not destroy).
/// </summary>
public bool Dormant { get; set; }
/// <summary>
/// Replication is being paused, but channel will not be closed.
/// </summary>
public bool bIsReplicationPaused { get; set; }
/// <summary>
/// Opened temporarily.
/// </summary>
public bool OpenTemporary { get; set; }
/// <summary>
/// Has encountered errors and is ignoring subsequent packets.
/// </summary>
public bool Broken { get; set; }
/// <summary>
/// Actor associated with this channel was torn off.
/// </summary>
public bool bTornOff { get; set; }
/// <summary>
/// Channel wants to go dormant (it will check during tick if it can go dormant).
/// </summary>
public bool bPendingDormancy { get; set; }
/// <summary>
/// Channel wants to go dormant, and is otherwise ready to become dormant, but is waiting for a timeout before doing so.
/// </summary>
public bool bIsInDormancyHysteresis { get; set; }
/// <summary>
/// Unreliable property replication is paused until all reliables are ack'd.
/// </summary>
public bool bPausedUntilReliableACK { get; set; }
/// <summary>
/// Set when sending closing bunch to avoid recursion in send-failure-close case.
/// </summary>
public bool SentClosingBunch { get; set; }
/// <summary>
/// Set when placed in the actor channel pool
/// </summary>
public bool bPooled { get; set; }
/// <summary>
/// Whether channel was opened locally or by remote.
/// </summary>
public bool OpenedLocally { get; set; }
/// <summary>
/// Whether channel was opened by replay checkpoint recording
/// </summary>
public bool bOpenedForCheckpoint { get; set; }
/// <summary>
/// Index of this channel.
/// </summary>
public int ChIndex { get; set; }
/// <summary>
/// If OpenedLocally is true, this is the packet we sent the bOpen bunch on.
/// Otherwise, it's the packet we received the bOpen bunch on.
/// </summary>
public FPacketIdRange OpenPacketId { get; set; }
/// <summary>
/// Type of this channel.
/// </summary>
public EChannelType ChType { get; set; }
/// <summary>
/// Name of the type of this channel.
/// </summary>
public FName ChName { get; set; }
/// <summary>
/// Number of packets in InRec.
/// </summary>
public int NumInRec { get; set; }
/// <summary>
/// Number of packets in OutRec.
/// </summary>
public int NumOutRec { get; set; }
/// <summary>
/// Incoming data with queued dependencies.
/// </summary>
public FInBunch? InRec { get; set; }
// public FOutBunch OutRec { get; set; }
/// <summary>
/// Partial bunch we are receiving (incoming partial bunches are appended to this).
/// </summary>
public FInBunch? InPartialBunch { get; set; }
public virtual void Init(UNetConnection inConnection, int inChIndex, EChannelCreateFlags createFlags)
{
Connection = inConnection;
ChIndex = inChIndex;
OpenedLocally = (createFlags & EChannelCreateFlags.OpenedLocally) != 0;
OpenPacketId = new FPacketIdRange();
bPausedUntilReliableACK = false;
SentClosingBunch = false;
}
public void ReceivedRawBunch(FInBunch bunch, out bool bOutSkipAck)
{
bOutSkipAck = false;
// Immediately consume the NetGUID portion of this bunch, regardless if it is partial or reliable.
// NOTE - For replays, we do this even earlier, to try and load this as soon as possible, in case there is an issue creating the channel
// If a replay fails to create a channel, we want to salvage as much as possible
if (bunch.bHasPackageMapExports && !Connection.IsInternalAck())
{
throw new NotImplementedException();
}
if (Connection.IsInternalAck() && Broken)
{
return;
}
if (bunch.bReliable && bunch.ChSequence != Connection.InReliable[ChIndex] + 1)
{
if (Connection.IsInternalAck())
{
throw new UnrealNetException("Shouldn't hit this path on 100% reliable connections");
}
if (bunch.ChSequence <= Connection.InReliable[ChIndex])
{
throw new UnrealNetException("Invalid bunch");
}
// TODO: (InRec) Queue
throw new NotImplementedException();
}
else
{
var bDeleted = ReceivedNextBunch(bunch, out bOutSkipAck);
if (bunch.IsError())
{
Logger.Error("Bunch.IsError() after ReceivedNextBunch 1");
return;
}
if (bDeleted)
{
return;
}
// TODO: (InRec) Dispatch waiting bunches
while (InRec != null)
{
throw new NotImplementedException();
}
}
}
private bool ReceivedNextBunch(FInBunch bunch, out bool bOutSkipAck)
{
bOutSkipAck = false;
// We received the next bunch. Basically at this point:
// -We know this is in order if reliable
// -We dont know if this is partial or not
// If its not a partial bunch, of it completes a partial bunch, we can call ReceivedSequencedBunch to actually handle it
// Note this bunch's retirement.
if (bunch.bReliable)
{
// Reliables should be ordered properly at this point
if (bunch.ChSequence != Connection.InReliable[bunch.ChIndex] + 1)
{
throw new UnrealNetException("Reliables should be ordered properly at this point");
}
Connection.InReliable[bunch.ChIndex] = bunch.ChSequence;
}
var handleBunch = bunch;
if (bunch.bPartial)
{
handleBunch = null;
if (bunch.bPartialInitial)
{
// Create new InPartialBunch if this is the initial bunch of a new sequence.
if (InPartialBunch != null)
{
if (!InPartialBunch.bPartialFinal)
{
if (InPartialBunch.bReliable)
{
if (bunch.bReliable)
{
Logger.Warning("Reliable partial trying to destroy reliable partial 1");
bunch.SetError();
return false;
}
Logger.Information("Unreliable partial trying to destroy reliable partial 1");
bOutSkipAck = true;
return false;
}
// We didn't complete the last partial bunch - this isn't fatal since they can be unreliable, but may want to log it.
Logger.Verbose("Incomplete partial bunch. Channel: {ChIndex} ChSequence: {ChSequence}", InPartialBunch.ChIndex, InPartialBunch.ChSequence);
}
InPartialBunch = null;
}
InPartialBunch = new FInBunch(bunch, false);
if (!bunch.bHasPackageMapExports && bunch.GetBitsLeft() > 0)
{
if (bunch.GetBitsLeft() % 8 != 0)
{
Logger.Warning("Corrupt partial bunch. Initial partial bunches are expected to be byte-aligned. BitsLeft = {BitCount}", bunch.GetBitsLeft());
bunch.SetError();
return false;
}
InPartialBunch.AppendDataFromChecked(bunch.GetBufferPosChecked(), bunch.GetBuffer(), bunch.GetBitsLeft());
Log.Verbose("Received new partial bunch");
}
else
{
Log.Verbose("Received New partial bunch. It only contained NetGUIDs");
}
}
else
{
// Merge in next partial bunch to InPartialBunch if:
// -We have a valid InPartialBunch
// -The current InPartialBunch wasn't already complete
// -ChSequence is next in partial sequence
// -Reliability flag matches
var bSequenceMatches = false;
if (InPartialBunch != null)
{
var bReliableSequencesMatches = bunch.ChSequence == InPartialBunch.ChSequence + 1;
var bUnreliableSequenceMatches = bReliableSequencesMatches || bunch.ChSequence == InPartialBunch.ChSequence;
// Unreliable partial bunches use the packet sequence, and since we can merge multiple bunches into a single packet,
// it's perfectly legal for the ChSequence to match in this case.
// Reliable partial bunches must be in consecutive order though
bSequenceMatches = InPartialBunch.bReliable ? bReliableSequencesMatches : bUnreliableSequenceMatches;
}
if (InPartialBunch != null && !InPartialBunch.bPartialFinal && bSequenceMatches && InPartialBunch.bReliable == bunch.bReliable)
{
// Merge.
Logger.Verbose("Merging Partial Bunch: {BytesLeft} Bytes", bunch.GetBytesLeft());
if (!bunch.bHasPackageMapExports && bunch.GetBitsLeft() > 0)
{
// TODO: Check if works.
InPartialBunch.AppendDataFromChecked(bunch.GetBufferPosChecked(), bunch.GetBuffer(), bunch.GetBitsLeft());
}
// Only the final partial bunch should ever be non byte aligned. This is enforced during partial bunch creation
// This is to ensure fast copies/appending of partial bunches. The final partial bunch may be non byte aligned.
if (!bunch.bHasPackageMapExports && !bunch.bPartialFinal && bunch.GetBitsLeft() % 8 != 0)
{
Logger.Warning("Corrupt partial bunch. Non-final partial bunches are expected to be byte-aligned. bHasPackageMapExports = {HasPackageMapExports}, bPartialFinal = {PartialFinal}, BitsLeft = {BitsLeft}",
bunch.bHasPackageMapExports ? 1 : 0,
bunch.bPartialFinal ? 1 : 0,
bunch.GetBitsLeft());
bunch.SetError();
return false;
}
// Advance the sequence of the current partial bunch so we know what to expect next
InPartialBunch.ChSequence = bunch.ChSequence;
if (bunch.bPartialFinal)
{
Logger.Verbose("Completed Partial Bunch ({BytesLeft} left)", bunch.GetBytesLeft());
if (bunch.bHasPackageMapExports)
{
// Shouldn't have these, they only go in initial partial export bunches
Logger.Warning("Corrupt partial bunch. Final partial bunch has package map exports");
bunch.SetError();
return false;
}
handleBunch = InPartialBunch;
InPartialBunch.bPartialFinal = true;
InPartialBunch.bClose = bunch.bClose;
InPartialBunch.bDormant = bunch.bDormant;
InPartialBunch.CloseReason = bunch.CloseReason;
InPartialBunch.bIsReplicationPaused = bunch.bIsReplicationPaused;
InPartialBunch.bHasMustBeMappedGUIDs = bunch.bHasMustBeMappedGUIDs;
}
else
{
Logger.Verbose("Received Partial Bunch");
}
}
else
{
// Merge problem - delete InPartialBunch.
// This is mainly so that in the unlikely chance that ChSequence wraps around, we wont merge two completely separate partial bunches.
// We shouldn't hit this path on 100% reliable connections
if (Connection.IsInternalAck())
{
throw new UnrealNetException("We shouldn't hit this path on 100% reliable connections");
}
bOutSkipAck = true; // Don't ack the packet, since we didn't process the bunch
if (InPartialBunch != null && InPartialBunch.bReliable)
{
if (bunch.bReliable)
{
Logger.Warning("Reliable partial trying to destroy reliable partial 2");
bunch.SetError();
return false;
}
Logger.Warning("Unreliable partial trying to destroy reliable partial 2");
return false;
}
if (InPartialBunch != null)
{
InPartialBunch = null;
}
}
}
// Fairly large number, and probably a bad idea to even have a bunch this size, but want to be safe for now and not throw out legitimate data
if (IsBunchTooLarge(Connection, InPartialBunch))
{
Logger.Error("Received a partial bunch exceeding max allowed size. BunchSize={Size}, MaximumSize={MaxSize}", InPartialBunch!.GetNumBytes(), NetMaxConstructedPartialBunchSizeBytes);
bunch.SetError();
return false;
}
}
if (handleBunch != null)
{
var bBothSidesCanOpen = Connection.Driver != null &&
Connection.Driver.ChannelDefinitionMap[ChName].ServerOpen &&
Connection.Driver.ChannelDefinitionMap[ChName].ClientOpen;
if (handleBunch.bOpen)
{
// Voice channels can open from both side simultaneously, so ignore this logic until we resolve this
if (!bBothSidesCanOpen)
{
// If we opened the channel, we shouldn't be receiving bOpen commands from the other side
if (OpenedLocally)
{
throw new UnrealNetException("Received channel open command for channel that was already opened locally.");
}
if (OpenPacketId.First != UnrealConstants.IndexNone || OpenPacketId.Last != UnrealConstants.IndexNone)
{
Logger.Error("This should be the first and only assignment of the packet range (we should only receive one bOpen bunch)");
bunch.SetError();
return false;
}
}
// Remember the range.
// In the case of a non partial, HandleBunch == Bunch
// In the case of a partial, HandleBunch should == InPartialBunch, and Bunch should be the last bunch.
OpenPacketId.First = handleBunch.PacketId;
OpenPacketId.Last = bunch.PacketId;
OpenAcked = true;
Logger.Verbose("ReceivedNextBunch: Channel now fully open. ChIndex: {ChIndex}, OpenPacketId.First: {First}, OpenPacketId.Last: {Last}", ChIndex, OpenPacketId.First, OpenPacketId.Last);
}
// Voice channels can open from both side simultaneously, so ignore this logic until we resolve this
if (!bBothSidesCanOpen)
{
// Don't process any packets until we've fully opened this channel
// (unless we opened it locally, in which case it's safe to process packets)
if (!OpenedLocally && !OpenAcked)
{
if (handleBunch.bReliable)
{
Logger.Error("ReceivedNextBunch: Reliable bunch before channel was fully open");
bunch.SetError();
return false;
}
if (Connection.IsInternalAck())
{
// Shouldn't be possible for 100% reliable connections
Broken = true;
return false;
}
// Don't ack this packet (since we won't process all of it)
bOutSkipAck = true;
Logger.Verbose("ReceivedNextBunch: Skipping bunch since channel isn't fully open. ChIndex: {ChIndex}", ChIndex);
return false;
}
// At this point, we should have the open packet range
// This is because if we opened the channel locally, we set it immediately when we sent the first bOpen bunch
// If we opened it from a remote connection, then we shouldn't be processing any packets until it's fully opened (which is handled above)
if (OpenPacketId.First == -1)
{
throw new UnrealNetException("Should have open packet range.");
}
if (OpenPacketId.Last == -1)
{
throw new UnrealNetException("Should have open packet range.");
}
}
// Receive it in sequence.
return ReceivedSequencedBunch(handleBunch);
}
return false;
}
private bool ReceivedSequencedBunch(FInBunch bunch)
{
// Handle a regular bunch.
if (!Closing)
{
ReceivedBunch(bunch);
}
// We have fully received the bunch, so process it.
if (bunch.bClose)
{
Dormant = bunch.bDormant || (bunch.CloseReason == EChannelCloseReason.Dormancy);
if (InRec != null)
{
Logger.Warning("Close Anomaly {Seq} / {InRecSeq}", bunch.ChSequence, InRec.ChSequence);
}
if (ChIndex == 0)
{
Logger.Debug("UChannel::ReceivedSequencedBunch: Bunch.bClose == true. ChIndex == 0. Calling ConditionalCleanUp");
}
ConditionalCleanUp(false, bunch.CloseReason);
return true;
}
return false;
}
protected abstract void ReceivedBunch(FInBunch bunch);
public void ConditionalCleanUp(bool bForDestroy, EChannelCloseReason closeReason)
{
throw new NotImplementedException();
}
private static bool IsBunchTooLarge(UNetConnection connection, FInBunch? bunch)
{
return !connection.IsInternalAck() && bunch != null && bunch.GetNumBytes() > NetMaxConstructedPartialBunchSizeBytes;
}
}
@@ -0,0 +1,18 @@
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Net.Packets.Bunch;
namespace Prospect.Unreal.Net.Channels;
public class UControlChannel : UChannel
{
public UControlChannel()
{
ChType = EChannelType.CHTYPE_Control;
ChName = UnrealNames.FNames[UnrealNameKey.Control];
}
protected override void ReceivedBunch(FInBunch bunch)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,18 @@
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Net.Packets.Bunch;
namespace Prospect.Unreal.Net.Channels;
public class UVoiceChannel : UChannel
{
public UVoiceChannel()
{
ChType = EChannelType.CHTYPE_Voice;
ChName = UnrealNames.FNames[UnrealNameKey.Control];
}
protected override void ReceivedBunch(FInBunch bunch)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,11 @@
namespace Prospect.Unreal.Net;
public enum EAcceptConnection
{
/** Reject the connection */
Reject,
/** Accept the connection */
Accept,
/** Ignore the connection, sending no reply, while server traveling */
Ignore
}
@@ -0,0 +1,15 @@
using Prospect.Unreal.Net.Channels;
namespace Prospect.Unreal.Net;
public readonly struct FChannelCloseInfo
{
public FChannelCloseInfo(uint id, EChannelCloseReason closeReason)
{
Id = id;
CloseReason = closeReason;
}
public uint Id { get; }
public EChannelCloseReason CloseReason { get; }
}
@@ -1,3 +1,5 @@
namespace Prospect.Unreal.Net; using Prospect.Unreal.Core.Names;
public record FChannelDefinition(string Name, Type Class, int StaticChannelIndex, bool TickOnCreate, bool ServerOpen, bool ClientOpen, bool InitialServer, bool InitialClient); namespace Prospect.Unreal.Net;
public record FChannelDefinition(FName Name, Type Class, int StaticChannelIndex, bool TickOnCreate, bool ServerOpen, bool ClientOpen, bool InitialServer, bool InitialClient);
+28
View File
@@ -0,0 +1,28 @@
using Prospect.Unreal.Net.Channels;
using Prospect.Unreal.Net.Packets.Bunch;
namespace Prospect.Unreal.Net;
public interface FNetworkNotify
{
EAcceptConnection NotifyAcceptingConnection();
/// <summary>
/// Notification that a new connection has been created/established as a result of a
/// remote request, previously approved by NotifyAcceptingConnection
/// </summary>
void NotifyAcceptedConnection(UNetConnection connection);
/// <summary>
/// Notification that a new channel is being created/opened as a result of a remote request (Actor creation, etc)
/// </summary>
bool NotifyAcceptingChannel(UChannel channel);
/// <summary>
/// Handler for messages sent through a remote connection's control channel not required to handle the message,
/// but if it reads any data from Bunch, it MUST read the ENTIRE data stream for that message
///
/// (i.e. use FNetControlMessage::Receive())
/// </summary>
void NotifyControlMessage(UNetConnection connection, byte messageType, FInBunch bunch);
}
+12
View File
@@ -0,0 +1,12 @@
namespace Prospect.Unreal.Net;
public class FPacketIdRange
{
public int First = -1;
public int Last = -1;
public bool InRange(int packetId)
{
return First <= packetId && packetId <= Last;
}
}
@@ -6,14 +6,46 @@ namespace Prospect.Unreal.Net.Packets.Bunch
{ {
public class FInBunch : FNetBitReader public class FInBunch : FNetBitReader
{ {
public FInBunch(FInBunch inBunch, bool copyBuffer) : base(inBunch.PackageMap, null, 0)
{
PacketId = inBunch.PacketId;
Next = inBunch.Next;
Connection = inBunch.Connection;
ChIndex = inBunch.ChIndex;
ChType = inBunch.ChType;
ChName = inBunch.ChName;
ChSequence = inBunch.ChSequence;
bOpen = inBunch.bOpen;
bClose = inBunch.bClose;
bDormant = inBunch.bDormant;
bIsReplicationPaused = inBunch.bIsReplicationPaused;
bReliable = inBunch.bReliable;
bPartial = inBunch.bPartial;
bPartialInitial = inBunch.bPartialInitial;
bPartialFinal = inBunch.bPartialFinal;
bHasPackageMapExports = inBunch.bHasPackageMapExports;
bHasMustBeMappedGUIDs = inBunch.bHasMustBeMappedGUIDs;
bIgnoreRPCs = inBunch.bIgnoreRPCs;
CloseReason = inBunch.CloseReason;
// Copy network version info
SetEngineNetVer(inBunch.EngineNetVer());
SetGameNetVer(inBunch.GameNetVer());
if (copyBuffer)
{
throw new NotImplementedException();
}
}
public FInBunch(UNetConnection inConnection, byte[]? src = null, int countBits = 0) : base(inConnection.PackageMap, src, countBits) public FInBunch(UNetConnection inConnection, byte[]? src = null, int countBits = 0) : base(inConnection.PackageMap, src, countBits)
{ {
PacketId = 0; PacketId = 0;
Next = null; Next = null;
Connection = inConnection; Connection = inConnection;
ChIndex = 0; ChIndex = 0;
ChType = 0; // TODO: CHTYPE_None ChType = EChannelType.CHTYPE_None;
ChName = UnrealNames.FNames[UnrealNameKey.None]; // TODO: NAME_None ChName = UnrealNames.FNames[UnrealNameKey.None];
ChSequence = 0; ChSequence = 0;
bOpen = false; bOpen = false;
bClose = false; bClose = false;
@@ -41,7 +73,7 @@ namespace Prospect.Unreal.Net.Packets.Bunch
public int ChIndex { get; set; } public int ChIndex { get; set; }
public int ChType { get; set; } public EChannelType ChType { get; set; }
public FName ChName { get; set; } public FName ChName { get; set; }
@@ -71,7 +71,7 @@ public class FNetPacketNotify
var inSeqDelta = GetSequenceDelta(notificationData); var inSeqDelta = GetSequenceDelta(notificationData);
if (inSeqDelta > 0) if (inSeqDelta > 0)
{ {
Logger.Verbose("FNetPacketNotify::Update - Seq {Seq}, InSeq {InSeq}", notificationData.Seq.Value, _inSeq.Value); Logger.Verbose("Update - Seq {Seq}, InSeq {InSeq}", notificationData.Seq.Value, _inSeq.Value);
ProcessReceivedAcks(notificationData, func); ProcessReceivedAcks(notificationData, func);
@@ -143,4 +143,34 @@ public class FNetPacketNotify
return new SequenceNumber((ushort)(ackedSeq.Value - MaxSequenceHistoryLength)); return new SequenceNumber((ushort)(ackedSeq.Value - MaxSequenceHistoryLength));
} }
/// <summary>
/// Mark Seq as received and update current InSeq, missing sequence numbers will be marked as lost
/// </summary>
public void AckSeq(SequenceNumber seq)
{
AckSeq(seq, true);
}
/// <summary>
/// Explicitly mark Seq as not received and update current InSeq, additional missing sequence numbers will be marked as lost
/// </summary>
public void NakSeq(SequenceNumber seq)
{
AckSeq(seq, false);
}
private void AckSeq(SequenceNumber ackedSeq, bool isAck)
{
while (ackedSeq.Greater(_inAckSeq))
{
_inAckSeq.IncrementAndGet();
var bReportAcked = _inAckSeq.Equals(ackedSeq) ? isAck : false;
Logger.Verbose("AckSeq - AckedSeq: {Seq}, IsAck {IsAck}", _inAckSeq.Value, bReportAcked);
_inSeqHistory.AddDeliveryStatus(bReportAcked);
}
}
} }
@@ -18,15 +18,6 @@ public readonly struct SequenceHistory
{ {
_storage = new uint[WordCount]; _storage = new uint[WordCount];
} }
public void Read(FBitReader reader, uint numWords)
{
numWords = Math.Min(numWords, WordCount);
for (var i = 0; i < numWords; i++)
{
_storage[i] = reader.ReadUInt32();
}
}
public void Reset() public void Reset()
{ {
@@ -36,6 +27,20 @@ public readonly struct SequenceHistory
} }
} }
public void AddDeliveryStatus(bool delivered)
{
var carry = delivered ? 1u : 0u;
var valueMask = 1u << (int)(BitsPerWord - 1);
for (var i = 0; i < WordCount; i++)
{
var oldValue = carry;
carry = (_storage[i] & valueMask) >> (int)(BitsPerWord - 1);
_storage[i] = (_storage[i] << 1) | oldValue;
}
}
public bool IsDelivered(int index) public bool IsDelivered(int index)
{ {
var wordIndex = (int)(index / BitsPerWord); var wordIndex = (int)(index / BitsPerWord);
@@ -43,4 +48,13 @@ public readonly struct SequenceHistory
return (_storage[wordIndex] & wordMask) != 0; return (_storage[wordIndex] & wordMask) != 0;
} }
public void Read(FBitReader reader, uint numWords)
{
numWords = Math.Min(numWords, WordCount);
for (var i = 0; i < numWords; i++)
{
_storage[i] = reader.ReadUInt32();
}
}
} }
@@ -0,0 +1,6 @@
namespace Prospect.Unreal.Net.Player;
public class APlayerController
{
}
@@ -0,0 +1,6 @@
namespace Prospect.Unreal.Net.Player;
public class UPlayer
{
public APlayerController? PlayerController { get; set; }
}
-8
View File
@@ -1,8 +0,0 @@
using Prospect.Unreal.Core.Names;
namespace Prospect.Unreal.Net;
public class UChannel
{
public FName ChName { get; }
}
+7 -1
View File
@@ -55,7 +55,13 @@ public class UIpConnection : UNetConnection
public override string LowLevelGetRemoteAddress(bool bAppendPort = false) public override string LowLevelGetRemoteAddress(bool bAppendPort = false)
{ {
throw new NotImplementedException(); if (RemoteAddr != null)
{
// TODO: Remove port
return RemoteAddr.ToString();
}
return string.Empty;
} }
public override string LowLevelDescribe() public override string LowLevelDescribe()
+6 -1
View File
@@ -22,8 +22,13 @@ public class UIpNetDriver : UNetDriver
public UdpClient Socket { get; } public UdpClient Socket { get; }
public FReceiveThreadRunnable ReceiveThread { get; } public FReceiveThreadRunnable ReceiveThread { get; }
public override bool Init() public override bool Init(FNetworkNotify notify)
{ {
if (!base.Init(notify))
{
return false;
}
// Initialize connectionless packet handler. // Initialize connectionless packet handler.
InitConnectionlessHandler(); InitConnectionlessHandler();
+338 -9
View File
@@ -1,4 +1,5 @@
using System.Net; using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using Prospect.Unreal.Core; using Prospect.Unreal.Core;
using Prospect.Unreal.Core.Names; using Prospect.Unreal.Core.Names;
@@ -7,12 +8,13 @@ using Prospect.Unreal.Net.Channels;
using Prospect.Unreal.Net.Packets.Bunch; using Prospect.Unreal.Net.Packets.Bunch;
using Prospect.Unreal.Net.Packets.Header; using Prospect.Unreal.Net.Packets.Header;
using Prospect.Unreal.Net.Packets.Header.Sequence; using Prospect.Unreal.Net.Packets.Header.Sequence;
using Prospect.Unreal.Net.Player;
using Prospect.Unreal.Serialization; using Prospect.Unreal.Serialization;
using Serilog; using Serilog;
namespace Prospect.Unreal.Net; namespace Prospect.Unreal.Net;
public abstract class UNetConnection public abstract class UNetConnection : UPlayer
{ {
private static readonly ILogger Logger = Log.ForContext<UNetConnection>(); private static readonly ILogger Logger = Log.ForContext<UNetConnection>();
@@ -30,6 +32,7 @@ public abstract class UNetConnection
public const EEngineNetworkVersionHistory DefaultEngineNetworkProtocolVersion = EEngineNetworkVersionHistory.HISTORY_ENGINENETVERSION_LATEST; public const EEngineNetworkVersionHistory DefaultEngineNetworkProtocolVersion = EEngineNetworkVersionHistory.HISTORY_ENGINENETVERSION_LATEST;
public const uint DefaultGameNetworkProtocolVersion = 0; public const uint DefaultGameNetworkProtocolVersion = 0;
private HashSet<UChannel> _channelsToTick;
private List<FBitReader>? _packetOrderCache; private List<FBitReader>? _packetOrderCache;
private int _packetOrderCacheStartIdx; private int _packetOrderCacheStartIdx;
private int _packetOrderCacheCount; private int _packetOrderCacheCount;
@@ -38,8 +41,17 @@ public abstract class UNetConnection
private bool _bInternalAck; private bool _bInternalAck;
private bool _bReplay; private bool _bReplay;
private double _statUpdateTime;
private double _lastReceiveTime;
private double _lastReceiveRealTime;
private double _lastGoodPacketRealtime;
private double _lastTime;
private double _lastSendTime;
private double _lastTickTime;
public UNetConnection() public UNetConnection()
{ {
_channelsToTick = new HashSet<UChannel>();
_packetOrderCache = null; _packetOrderCache = null;
_packetOrderCacheStartIdx = 0; _packetOrderCacheStartIdx = 0;
_packetOrderCacheCount = 0; _packetOrderCacheCount = 0;
@@ -49,6 +61,7 @@ public abstract class UNetConnection
Driver = null; Driver = null;
PackageMap = null; PackageMap = null;
OpenChannels = new List<UChannel>();
MaxPacket = 0; MaxPacket = 0;
Url = new FUrl(); Url = new FUrl();
RemoteAddr = new IPEndPoint(IPAddress.None, 0); RemoteAddr = new IPEndPoint(IPAddress.None, 0);
@@ -85,6 +98,8 @@ public abstract class UNetConnection
/// </summary> /// </summary>
public UPackageMapClient? PackageMap { get; private set; } public UPackageMapClient? PackageMap { get; private set; }
public List<UChannel> OpenChannels { get; private set; }
/// <summary> /// <summary>
/// Maximum packet size. /// Maximum packet size.
/// </summary> /// </summary>
@@ -168,7 +183,17 @@ public abstract class UNetConnection
Driver = inDriver; Driver = inDriver;
// TODO: ConnectionId // TODO: ConnectionId
var driverElapsedTime = Driver.GetElapsedTime();
_statUpdateTime = driverElapsedTime;
_lastReceiveTime = driverElapsedTime;
_lastReceiveRealTime = 0;
_lastGoodPacketRealtime = 0;
_lastTime = 0;
_lastSendTime = driverElapsedTime;
_lastTickTime = driverElapsedTime;
State = inState; State = inState;
Url.Protocol = inURL.Protocol; Url.Protocol = inURL.Protocol;
@@ -263,7 +288,8 @@ public abstract class UNetConnection
} }
var resetReaderMark = new FBitReaderMark(reader); var resetReaderMark = new FBitReaderMark(reader);
var channelsToClose = new List<FChannelCloseInfo>();
if (_bInternalAck) if (_bInternalAck)
{ {
++InPacketId; ++InPacketId;
@@ -369,7 +395,7 @@ public abstract class UNetConnection
} }
var bIgnoreRPCS = Driver!.ShouldIgnoreRPCs(); var bIgnoreRPCS = Driver!.ShouldIgnoreRPCs();
var bSickAcp = false; var bSkipAck = false;
// Track channels that were rejected while processing this packet - used to avoid sending multiple close-channel bunches, // Track channels that were rejected while processing this packet - used to avoid sending multiple close-channel bunches,
// which would cause a disconnect serverside // which would cause a disconnect serverside
@@ -471,7 +497,7 @@ public abstract class UNetConnection
if (bunch.EngineNetVer() < EEngineNetworkVersionHistory.HISTORY_CHANNEL_NAMES) if (bunch.EngineNetVer() < EEngineNetworkVersionHistory.HISTORY_CHANNEL_NAMES)
{ {
bunch.ChType = (bunch.bReliable || bunch.bOpen) ? (int) reader.ReadInt(EChannelType.CHTYPE_MAX) : EChannelType.CHTYPE_None; bunch.ChType = ((bunch.bReliable || bunch.bOpen) ? (EChannelType) reader.ReadInt((int) EChannelType.CHTYPE_MAX) : EChannelType.CHTYPE_None);
switch (bunch.ChType) switch (bunch.ChType)
{ {
@@ -533,10 +559,225 @@ public abstract class UNetConnection
// TODO: Close(); // TODO: Close();
return; return;
} }
var bunchDataBits = reader.ReadInt((uint)(MaxPacket * 8));
var headerPos = reader.GetPosBits();
if (reader.IsError())
{
Logger.Error("Bunch header overflow");
// TODO: Close();
return;
}
bunch.SetData(reader, bunchDataBits);
if (reader.IsError())
{
Logger.Fatal("Bunch data overflowed ({IncomingStartPos} {HeaderPos}+{BunchDataBits}/{NumBits})", incomingStartPos, headerPos, bunchDataBits, reader.GetNumBits());
// TOOD: Close();
return;
}
if (bunch.bHasPackageMapExports)
{
throw new NotImplementedException();
}
if (bunch.bReliable)
{
Logger.Verbose(" Reliable Bunch, Channel {Ch} Sequence {Seq}: Size {A.0}+{B.0}", bunch.ChIndex, bunch.ChSequence, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
}
else
{
Logger.Verbose(" Unreliable Bunch, Channel {Ch}: Size {A.0}+{B.0}", bunch.ChIndex, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
}
if (bunch.bOpen)
{
Logger.Verbose(" bOpen Bunch, Channel {Ch} Sequence {Seq}: Size {A.0}+{B.0}", bunch.ChIndex, bunch.ChSequence, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
}
if (Channels[bunch.ChIndex] == null && (bunch.ChIndex != 0 || bunch.ChName != UnrealNames.FNames[UnrealNameKey.Control]))
{
if (Channels[0] == null)
{
Logger.Fatal(" Received non-control bunch before control channel was created. ChIndex: {Ch}, ChName: {Name}", bunch.ChIndex, bunch.ChName);
// TODO: Close();
return;
}
else if (PlayerController == null && Driver.ClientConnections.Contains(this))
{
Logger.Fatal(" Received non-control bunch before player controller was assigned. ChIndex: {Ch}, ChName: {Name}", bunch.ChIndex, bunch.ChName);
// TODO: Close();
return;
}
}
// ignore control channel close if it hasn't been opened yet
if (bunch.ChIndex == 0 && Channels[0] == null && bunch.bClose && bunch.ChName == UnrealNames.FNames[UnrealNameKey.Control])
{
Logger.Fatal("Received control channel close before open");
// Close();
return;
}
// We're on a 100% reliable connection and we are rolling back some data.
// In that case, we can generally ignore these bunches.
if (_bInternalAck /* && _bAllowExistingChannelIndex */)
{
throw new NotImplementedException();
}
// Ignore if reliable packet has already been processed.
if (bunch.bReliable && bunch.ChSequence <= InReliable[bunch.ChIndex])
{
Logger.Warning("Received outdated bunch (Channel {Ch} Current Sequence {Seq})", bunch.ChIndex, InReliable[bunch.ChIndex]);
continue;
}
// If opening the channel with an unreliable packet, check that it is "bNetTemporary", otherwise discard it
if (channel == null && !bunch.bReliable)
{
// Unreliable bunches that open channels should be bOpen && (bClose || bPartial)
// NetTemporary usually means one bunch that is unreliable (bOpen and bClose): 1(bOpen, bClose)
// But if that bunch export NetGUIDs, it will get split into 2 bunches: 1(bOpen, bPartial) - 2(bClose).
// (the initial actor bunch itself could also be split into multiple bunches. So bPartial is the right check here)
var validUnreliableOpen = bunch.bOpen && (bunch.bClose || bunch.bPartial);
if (!validUnreliableOpen)
{
if (_bInternalAck)
{
// Should be impossible with 100% reliable connections
Logger.Error("Received unreliable bunch before open with reliable connection (Channel {Ch} Current Sequence {Seq})", bunch.ChIndex, InReliable[bunch.ChIndex]);
}
else
{
// Simply a log (not a warning, since this can happen under normal conditions, like from a re-join, etc)
Logger.Information("Received unreliable bunch before open (Channel {Ch} Current Sequence {Seq})", bunch.ChIndex, InReliable[bunch.ChIndex]);
}
// Since we won't be processing this packet, don't ack it
// We don't want the sender to think this bunch was processed when it really wasn't
bSkipAck = true;
continue;
}
}
// Create channel if necessary.
if (channel == null)
{
if (rejectedChans.Contains(bunch.ChIndex))
{
Logger.Warning("Ignoring Bunch for ChIndex {Ch}, as the channel was already rejected while processing this packet", bunch.ChIndex);
continue;
}
// Validate channel type.
if (!Driver.IsKnownChannelName(bunch.ChName))
{
// Unknown type.
Logger.Fatal("Connection unknown channel type ({Name})", bunch.ChName);
// TODO: Close()
return;
}
// Ignore incoming data on channel types that the client are not allowed to create. This can occur if we have in-flight data when server is closing a channel
if (Driver.IsServer() && (Driver.ChannelDefinitionMap[bunch.ChName].ClientOpen == false))
{
Logger.Warning("Ignoring Bunch Create received from client since only server is allowed to create this type of channel: Bunch {Ch}: ChName {Name}, ChSequence: {Seq}", bunch.ChIndex, bunch.ChName, bunch.ChSequence);
rejectedChans.Add(bunch.ChIndex);
continue;
}
// peek for guid
if (_bInternalAck /* && bIgnoreActorBunches */)
{
throw new NotImplementedException();
}
// Reliable (either open or later), so create new channel.
Logger.Information(" Bunch Create {ChIndex}: ChName {ChName}, ChSequence: {ChSequence}, bReliable: {Reliable}, bPartial: {Partial}, bPartialInitial: {PartInit}, bPartialFinal: {PartFin}",
bunch.ChIndex,
bunch.ChName,
bunch.ChSequence,
bunch.bReliable,
bunch.bPartial,
bunch.bPartialInitial,
bunch.bPartialFinal);
channel = CreateChannelByName(bunch.ChName, EChannelCreateFlags.None, bunch.ChIndex);
// Notify the server of the new channel.
if (!Driver.Notify.NotifyAcceptingChannel(channel))
{
// Channel refused, so close it, flush it, and delete it.
Logger.Verbose("NotifyAcceptingChannel Failed! Channel: {Channel}", channel);
rejectedChans.Add(bunch.ChIndex);
// TODO: FOutBunch
continue;
}
}
bunch.bIgnoreRPCs = bIgnoreRPCS;
// Dispatch the raw, unsequenced bunch to the channel.
channel.ReceivedRawBunch(bunch, out var bLocalSkipAck);
if (bLocalSkipAck)
{
bSkipAck = true;
}
// Disconnect if we received a corrupted packet from the client (eg server crash attempt).
if (Driver.IsServer() && (bunch.IsCriticalError() || bunch.IsError()))
{
Logger.Error("Received corrupted packet data from client {RemoteAddress}. Disconnecting", LowLevelGetRemoteAddress());
// TODO: Close()
return;
}
} }
} }
throw new NotImplementedException(); // Close/clean-up channels pending close due to received acks.
foreach (var info in channelsToClose)
{
var channel = Channels[info.Id];
if (channel != null)
{
channel.ConditionalCleanUp(false, info.CloseReason);
}
}
// TODO: ValidateSendBuffer();
if (!bSkipAck)
{
_lastGoodPacketRealtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
if (!_bInternalAck)
{
if (bSkipAck)
{
PacketNotify.NakSeq(new SequenceNumber((ushort)InPacketId));
}
else
{
PacketNotify.AckSeq(new SequenceNumber((ushort)InPacketId));
// TODO: Increment things
// ++OutTotalAcks;
// ++Driver->OutTotalAcks;
}
// We do want to let the other side know about the ack, so even if there are no other outgoing data when we tick the connection we will send an ackpacket.
// TimeSensitive = 1;
// ++HasDirtyAcks;
// TODO: FlushNet if HasDirtyAcks
}
} }
private bool ReadPacketInfo(FBitReader reader, bool bHasPacketInfoPayload) private bool ReadPacketInfo(FBitReader reader, bool bHasPacketInfoPayload)
@@ -641,10 +882,93 @@ public abstract class UNetConnection
Handler.InitializeComponents(); Handler.InitializeComponents();
} }
public void CreateChannelByName(string chName, EChannelCreateFlags createFlags, int channelIndex) public UChannel CreateChannelByName(FName chName, EChannelCreateFlags createFlags, int chIndex)
{ {
// TODO: Implement if (!Driver!.IsKnownChannelName(chName))
Logger.Verbose("Creating channel {Name} with index {Index}", chName, channelIndex); {
throw new UnrealNetException("Unknown channel name was specified.");
}
if (chIndex == UnrealConstants.IndexNone)
{
chIndex = GetFreeChannelIndex(chName);
if (chIndex == UnrealConstants.IndexNone)
{
Logger.Warning("No free channel could be found in the channel list (current limit is {Max} channels)", MaxChannelSize);
throw new UnrealNetException("Exhausted channels");
}
}
// Make sure channel is valid.
if (chIndex >= Channels.Length)
{
throw new UnrealNetException("Channel index is too high.");
}
if (Channels[chIndex] != null)
{
throw new UnrealNetException("Trying to replace an existing channel.");
}
// Create channel.
var channel = Driver.GetOrCreateChannelByName(chName);
if (channel == null)
{
throw new UnrealNetException("Failed to create channel.");
}
channel.Init(this, chIndex, createFlags);
Channels[chIndex] = channel;
OpenChannels.Add(channel);
if (Driver.ChannelDefinitionMap[chName].TickOnCreate)
{
StartTickingChannel(channel);
}
Logger.Verbose("Created channel {Ch} of type {Name}", chIndex, chName);
return channel;
}
private int GetFreeChannelIndex(FName chName)
{
int chIndex;
var firstChannel = 1;
var staticChannelIndex = Driver!.ChannelDefinitionMap[chName].StaticChannelIndex;
if (staticChannelIndex != -1)
{
firstChannel = staticChannelIndex;
}
// Search the channel array for an available location
for (chIndex = firstChannel; chIndex < Channels.Length; chIndex++)
{
if (Channels[chIndex] == null)
{
break;
}
}
if (chIndex == Channels.Length)
{
chIndex = UnrealConstants.IndexNone;
}
return chIndex;
}
private void StartTickingChannel(UChannel channel)
{
_channelsToTick.Add(channel);
}
private void StopTickingChannel(UChannel channel)
{
_channelsToTick.Remove(channel);
} }
private protected void SetClientLoginState(EClientLoginState newState) private protected void SetClientLoginState(EClientLoginState newState)
@@ -673,6 +997,11 @@ public abstract class UNetConnection
ExpectedClientLoginMsgType = newType; ExpectedClientLoginMsgType = newType;
} }
public bool IsInternalAck()
{
return _bInternalAck;
}
private static int BestSignedDifference(int value, int reference, int max) private static int BestSignedDifference(int value, int reference, int max)
{ {
return ((value - reference + max / 2) & (max - 1)) - max / 2; return ((value - reference + max / 2) & (max - 1)) - max / 2;
+39 -5
View File
@@ -1,5 +1,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Net; using System.Net;
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Exceptions;
using Prospect.Unreal.Net.Channels; using Prospect.Unreal.Net.Channels;
using Prospect.Unreal.Runtime; using Prospect.Unreal.Runtime;
@@ -13,14 +15,20 @@ public abstract class UNetDriver : IAsyncDisposable
{ {
GuidCache = new FNetGUIDCache(this); GuidCache = new FNetGUIDCache(this);
// TODO: Load from Engine ini // TODO: Load from Engine ini
ChannelDefinitionMap = new Dictionary<FName, FChannelDefinition>();
ChannelDefinitions = new List<FChannelDefinition> ChannelDefinitions = new List<FChannelDefinition>
{ {
new FChannelDefinition("Control", typeof(string), 0, true, false, true, false, true), new FChannelDefinition(UnrealNames.FNames[UnrealNameKey.Control], typeof(string), 0, true, false, true, false, true),
new FChannelDefinition("Voice", typeof(string), 1, true, true, true, true, true), new FChannelDefinition(UnrealNames.FNames[UnrealNameKey.Voice], typeof(string), 1, true, true, true, true, true),
new FChannelDefinition("Actor", typeof(string), -1, false, true, false, false, false) new FChannelDefinition(UnrealNames.FNames[UnrealNameKey.Actor], typeof(string), -1, false, true, false, false, false)
}; };
ClientConnections = new List<UNetConnection>(); ClientConnections = new List<UNetConnection>();
MappedClientConnections = new ConcurrentDictionary<IPEndPoint, UNetConnection>(); MappedClientConnections = new ConcurrentDictionary<IPEndPoint, UNetConnection>();
foreach (var channel in ChannelDefinitions)
{
ChannelDefinitionMap[channel.Name] = channel;
}
} }
/// <summary> /// <summary>
@@ -28,12 +36,19 @@ public abstract class UNetDriver : IAsyncDisposable
/// </summary> /// </summary>
public UWorld? World { get; private set; } public UWorld? World { get; private set; }
public FNetworkNotify Notify { get; private set; }
public FNetGUIDCache GuidCache { get; } public FNetGUIDCache GuidCache { get; }
/// <summary> /// <summary>
/// Used to specify available channel types and their associated UClass /// Used to specify available channel types and their associated UClass
/// </summary> /// </summary>
public List<FChannelDefinition> ChannelDefinitions { get; } public List<FChannelDefinition> ChannelDefinitions { get; }
/// <summary>
/// Used for faster lookup of channel definitions by name.
/// </summary>
public Dictionary<FName, FChannelDefinition> ChannelDefinitionMap { get; }
/// <summary> /// <summary>
/// Array of connections to clients (this net driver is a host) - unsorted, and ordering changes depending on actor replication /// Array of connections to clients (this net driver is a host) - unsorted, and ordering changes depending on actor replication
@@ -66,9 +81,10 @@ public abstract class UNetDriver : IAsyncDisposable
ConnectionlessHandler.InitializeComponents(); ConnectionlessHandler.InitializeComponents();
} }
public virtual bool Init() public virtual bool Init(FNetworkNotify notify)
{ {
throw new NotImplementedException(); Notify = notify;
return true;
} }
public virtual void TickDispatch(float deltaTime) public virtual void TickDispatch(float deltaTime)
@@ -103,6 +119,11 @@ public abstract class UNetDriver : IAsyncDisposable
return true; return true;
} }
public bool IsKnownChannelName(FName name)
{
return ChannelDefinitionMap.ContainsKey(name);
}
public virtual bool ShouldIgnoreRPCs() public virtual bool ShouldIgnoreRPCs()
{ {
return false; return false;
@@ -145,6 +166,19 @@ public abstract class UNetDriver : IAsyncDisposable
} }
} }
public UChannel GetOrCreateChannelByName(FName chName)
{
// TODO: Pool actor channels (?)
return (UnrealNameKey)chName.Number switch
{
UnrealNameKey.Actor => new UActorChannel(),
UnrealNameKey.Control => new UControlChannel(),
UnrealNameKey.Voice => new UVoiceChannel(),
_ => throw new UnrealNetException($"Attempted to create unknown channel {chName}")
};
}
public virtual ValueTask DisposeAsync() public virtual ValueTask DisposeAsync()
{ {
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
+42 -2
View File
@@ -1,10 +1,12 @@
using Prospect.Unreal.Core; using Prospect.Unreal.Core;
using Prospect.Unreal.Net; using Prospect.Unreal.Net;
using Prospect.Unreal.Net.Channels;
using Prospect.Unreal.Net.Packets.Bunch;
using Serilog; using Serilog;
namespace Prospect.Unreal.Runtime; namespace Prospect.Unreal.Runtime;
public abstract class UWorld : IAsyncDisposable public abstract class UWorld : FNetworkNotify, IAsyncDisposable
{ {
private static readonly ILogger Logger = Log.ForContext<UWorld>(); private static readonly ILogger Logger = Log.ForContext<UWorld>();
@@ -36,7 +38,7 @@ public abstract class UWorld : IAsyncDisposable
NetDriver = new UIpNetDriver(Url.Host, Url.Port); NetDriver = new UIpNetDriver(Url.Host, Url.Port);
NetDriver.SetWorld(this); NetDriver.SetWorld(this);
if (!NetDriver.Init()) if (!NetDriver.Init(this))
{ {
Logger.Error("Failed to listen"); Logger.Error("Failed to listen");
NetDriver = null; NetDriver = null;
@@ -46,6 +48,44 @@ public abstract class UWorld : IAsyncDisposable
return true; return true;
} }
public EAcceptConnection NotifyAcceptingConnection()
{
return EAcceptConnection.Accept;
}
public void NotifyAcceptedConnection(UNetConnection connection)
{
}
public bool NotifyAcceptingChannel(UChannel channel)
{
var driver = channel.Connection.Driver!;
if (!driver.IsServer())
{
throw new NotSupportedException("Client code");
}
else
{
// We are the server.
if (driver.ChannelDefinitionMap[channel.ChName].ClientOpen)
{
// The client has opened initial channel.
Logger.Verbose("NotifyAcceptingChannel {ChName} {ChIndex} server {FullName}: Accepted", channel.ChName, channel.ChIndex, typeof(UWorld).FullName);
return true;
}
// Client can't open any other kinds of channels.
Logger.Verbose("NotifyAcceptingChannel {ChName} {ChIndex} server {FullName}: Refused", channel.ChName, channel.ChIndex, typeof(UWorld).FullName);
return false;
}
}
public void NotifyControlMessage(UNetConnection connection, byte messageType, FInBunch bunch)
{
}
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
if (NetDriver != null) if (NetDriver != null)
@@ -1,6 +1,6 @@
namespace Prospect.Unreal.Serialization; namespace Prospect.Unreal.Serialization;
public class FBitReaderMark public struct FBitReaderMark
{ {
private long _pos; private long _pos;