CHANGED TO MIRROR

This commit is contained in:
DerTyp187
2021-10-25 09:20:01 +02:00
parent bd712107b7
commit e509a919b6
611 changed files with 38291 additions and 1216 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Mirror Networking (vis2k, FakeByte)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a857d4e863bbf4a7dba70bc2cd1b5949
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6b7f3f8e8fa16475bbe48a8e9fbe800b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("where-allocations.Tests")]

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 158a96a7489b450485a8b06a13328871
timeCreated: 1622356221

View File

@@ -0,0 +1,58 @@
using System.Net;
using System.Net.Sockets;
namespace WhereAllocation
{
public static class Extensions
{
// always pass the same IPEndPointNonAlloc instead of allocating a new
// one each time.
//
// use IPEndPointNonAlloc.temp to get the latest SocketAdddress written
// by ReceiveFrom_Internal!
//
// IMPORTANT: .temp will be overwritten in next call!
// hash or manually copy it if you need to store it, e.g.
// when adding a new connection.
public static int ReceiveFrom_NonAlloc(
this Socket socket,
byte[] buffer,
int offset,
int size,
SocketFlags socketFlags,
IPEndPointNonAlloc remoteEndPoint)
{
// call ReceiveFrom with IPEndPointNonAlloc.
// need to wrap this in ReceiveFrom_NonAlloc because it's not
// obvious that IPEndPointNonAlloc.Create does NOT create a new
// IPEndPoint. it saves the result in IPEndPointNonAlloc.temp!
EndPoint casted = remoteEndPoint;
return socket.ReceiveFrom(buffer, offset, size, socketFlags, ref casted);
}
// same as above, different parameters
public static int ReceiveFrom_NonAlloc(this Socket socket, byte[] buffer, IPEndPointNonAlloc remoteEndPoint)
{
EndPoint casted = remoteEndPoint;
return socket.ReceiveFrom(buffer, ref casted);
}
// SendTo allocates too:
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L2240
// -> the allocation is in EndPoint.Serialize()
// NOTE: technically this function isn't necessary.
// could just pass IPEndPointNonAlloc.
// still good for strong typing.
public static int SendTo_NonAlloc(
this Socket socket,
byte[] buffer,
int offset,
int size,
SocketFlags socketFlags,
IPEndPointNonAlloc remoteEndPoint)
{
EndPoint casted = remoteEndPoint;
return socket.SendTo(buffer, offset, size, socketFlags, casted);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e801942544d44d65808fb250623fe25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,208 @@
using System;
using System.Net;
using System.Net.Sockets;
namespace WhereAllocation
{
public class IPEndPointNonAlloc : IPEndPoint
{
// Two steps to remove allocations in ReceiveFrom_Internal:
//
// 1.) remoteEndPoint.Serialize():
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1733
// -> creates an EndPoint for ReceiveFrom_Internal to write into
// -> it's never read from:
// ReceiveFrom_Internal passes it to native:
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1885
// native recv populates 'sockaddr* from' with the remote address:
// https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom
// -> can NOT be null. bricks both Unity and Unity Hub otherwise.
// -> it seems as if Serialize() is only called to avoid allocating
// a 'new SocketAddress' in ReceiveFrom. it's up to the EndPoint.
//
// 2.) EndPoint.Create(SocketAddress):
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761
// -> SocketAddress is the remote's address that we want to return
// -> to avoid 'new EndPoint(SocketAddress), it seems up to the user
// to decide how to create a new EndPoint via .Create
// -> SocketAddress is the object that was returned by Serialize()
//
// in other words, all we need is an extra SocketAddress field that we
// can pass to ReceiveFrom_Internal to write the result into.
// => callers can then get the result from the extra field!
// => no allocations
//
// IMPORTANT: remember that IPEndPointNonAlloc is always the same object
// and never changes. only the helper field is changed.
public SocketAddress temp;
// constructors simply create the field once by calling the base method.
// (our overwritten method would create anything new)
public IPEndPointNonAlloc(long address, int port) : base(address, port)
{
temp = base.Serialize();
}
public IPEndPointNonAlloc(IPAddress address, int port) : base(address, port)
{
temp = base.Serialize();
}
// Serialize simply returns it
public override SocketAddress Serialize() => temp;
// Create doesn't need to create anything.
// SocketAddress object is already the one we returned in Serialize().
// ReceiveFrom_Internal simply wrote into it.
public override EndPoint Create(SocketAddress socketAddress)
{
// original IPEndPoint.Create validates:
if (socketAddress.Family != AddressFamily)
throw new ArgumentException($"Unsupported socketAddress.AddressFamily: {socketAddress.Family}. Expected: {AddressFamily}");
if (socketAddress.Size < 8)
throw new ArgumentException($"Unsupported socketAddress.Size: {socketAddress.Size}. Expected: <8");
// double check to guarantee that ReceiveFrom actually did write
// into our 'temp' field. just in case that's ever changed.
if (socketAddress != temp)
{
// well this is fun.
// in the latest mono from the above github links,
// the result of Serialize() is passed as 'ref' so ReceiveFrom
// does in fact write into it.
//
// in Unity 2019 LTS's mono version, it does create a new one
// each time. this is from ILSpy Receive_From:
//
// SocketPal.CheckDualModeReceiveSupport(this);
// ValidateBlockingMode();
// if (NetEventSource.IsEnabled)
// {
// NetEventSource.Info(this, $"SRC{LocalEndPoint} size:{size} remoteEP:{remoteEP}", "ReceiveFrom");
// }
// EndPoint remoteEP2 = remoteEP;
// System.Net.Internals.SocketAddress socketAddress = SnapshotAndSerialize(ref remoteEP2);
// System.Net.Internals.SocketAddress socketAddress2 = IPEndPointExtensions.Serialize(remoteEP2);
// int bytesTransferred;
// SocketError socketError = SocketPal.ReceiveFrom(_handle, buffer, offset, size, socketFlags, socketAddress.Buffer, ref socketAddress.InternalSize, out bytesTransferred);
// SocketException ex = null;
// if (socketError != 0)
// {
// ex = new SocketException((int)socketError);
// UpdateStatusAfterSocketError(ex);
// if (NetEventSource.IsEnabled)
// {
// NetEventSource.Error(this, ex, "ReceiveFrom");
// }
// if (ex.SocketErrorCode != SocketError.MessageSize)
// {
// throw ex;
// }
// }
// if (!socketAddress2.Equals(socketAddress))
// {
// try
// {
// remoteEP = remoteEP2.Create(socketAddress);
// }
// catch
// {
// }
// if (_rightEndPoint == null)
// {
// _rightEndPoint = remoteEP2;
// }
// }
// if (ex != null)
// {
// throw ex;
// }
// if (NetEventSource.IsEnabled)
// {
// NetEventSource.DumpBuffer(this, buffer, offset, size, "ReceiveFrom");
// NetEventSource.Exit(this, bytesTransferred, "ReceiveFrom");
// }
// return bytesTransferred;
//
// so until they upgrade their mono version, we are stuck with
// some allocations.
//
// for now, let's pass the newly created on to our temp so at
// least we reuse it next time.
temp = socketAddress;
// SocketAddress.GetHashCode() depends on SocketAddress.m_changed.
// ReceiveFrom only sets the buffer, it does not seem to set m_changed.
// we need to reset m_changed for two reasons:
// * if m_changed is false, GetHashCode() returns the cahced m_hash
// which is '0'. that would be a problem.
// https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L262
// * if we have a cached m_hash, but ReceiveFrom modified the buffer
// then the GetHashCode() should change too. so we need to reset
// either way.
//
// the only way to do that is by _actually_ modifying the buffer:
// https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L99
// so let's do that.
// -> unchecked in case it's byte.Max
unchecked
{
temp[0] += 1;
temp[0] -= 1;
}
// make sure this worked.
// at least throw an Exception to make it obvious if the trick does
// not work anymore, in case ReceiveFrom is ever changed.
if (temp.GetHashCode() == 0)
throw new Exception($"SocketAddress GetHashCode() is 0 after ReceiveFrom. Does the m_changed trick not work anymore?");
// in the future, enable this again:
//throw new Exception($"Socket.ReceiveFrom(): passed SocketAddress={socketAddress} but expected {temp}. This should never happen. Did ReceiveFrom() change?");
}
// ReceiveFrom sets seed_endpoint to the result of Create():
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1764
// so let's return ourselves at least.
// (seed_endpoint only seems to matter for BeginSend etc.)
return this;
}
// we need to overwrite GetHashCode() for two reasons.
// https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPEndPoint.cs#L160
// * it uses m_Address. but our true SocketAddress is in m_temp.
// m_Address might not be set at all.
// * m_Address.GetHashCode() allocates:
// https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699
public override int GetHashCode() => temp.GetHashCode();
// helper function to create an ACTUAL new IPEndPoint from this.
// server needs it to store new connections as unique IPEndPoints.
public IPEndPoint DeepCopyIPEndPoint()
{
// we need to create a new IPEndPoint from 'temp' SocketAddress.
// there is no 'new IPEndPoint(SocketAddress) constructor.
// so we need to be a bit creative...
// allocate a placeholder IPAddress to copy
// our SocketAddress into.
// -> needs to be the same address family.
IPAddress ipAddress;
if (temp.Family == AddressFamily.InterNetworkV6)
ipAddress = IPAddress.IPv6Any;
else if (temp.Family == AddressFamily.InterNetwork)
ipAddress = IPAddress.Any;
else
throw new Exception($"Unexpected SocketAddress family: {temp.Family}");
// allocate a placeholder IPEndPoint
// with the needed size form IPAddress.
// (the real class. not NonAlloc)
IPEndPoint placeholder = new IPEndPoint(ipAddress, 0);
// the real IPEndPoint's .Create function can create a new IPEndPoint
// copy from a SocketAddress.
return (IPEndPoint)placeholder.Create(temp);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af0279d15e39b484792394f1d3cad4d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
{
"name": "where-allocations",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 63c380d6dae6946209ed0832388a657c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,2 @@
V0.1 [2021-06-01]
- initial release

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f1256cadc037546ccb66071784fce137
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: