Skip to content

Commit

Permalink
ton more work in the right direction reduced gc pressure
Browse files Browse the repository at this point in the history
  • Loading branch information
dooly123 committed Jan 6, 2025
1 parent c3fe2a2 commit d6726cc
Show file tree
Hide file tree
Showing 9 changed files with 490 additions and 67 deletions.
4 changes: 3 additions & 1 deletion Basis Server/BasisNetworkClient/BasisNetworkClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Basis.Network.Core;
using BasisNetworkCore;
using LiteNetLib;
using LiteNetLib.Utils;
using static Basis.Network.Core.Serializable.SerializableBasis;
Expand Down Expand Up @@ -37,12 +38,13 @@ public static NetPeer StartClient(string IP, int port, ReadyMessage ReadyMessage
UnsyncedEvents = true,
};
client.Start();
NetDataWriter Writer = new NetDataWriter();
NetDataWriter Writer = NetDataWriterPool.GetWriter();
//this is the only time we dont put key!
Writer.Put(BasisNetworkVersion.ServerVersion);
AuthenticationMessage.Serialize(Writer);
ReadyMessage.Serialize(Writer);
peer = client.Connect(IP, port, Writer);
NetDataWriterPool.ReturnWriter(Writer);
return peer;
}
else
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
using Basis.Network.Core;
using Basis.Scripts.Networking.Compression;
using BasisNetworkCore;
using LiteNetLib;
using LiteNetLib.Utils;
using System.Reflection.PortableExecutable;
using System.Runtime.Intrinsics.X86;
using System.Text;
using static Basis.Network.Core.Serializable.SerializableBasis;
using static BasisNetworkPrimitiveCompression;
using static SerializableBasis;

namespace Basis
{
class Program
{
public static string Password = "default_password";
private static readonly object nameLock = new object(); // To synchronize name generation
public static NetPeer LocalPLayer;

Check warning on line 19 in Basis Server/BasisNetworkClientConsole/BasisNetworkClientConsole/Program.cs

View workflow job for this annotation

GitHub Actions / Build server on Ubuntu (Basis Server, linux)

Non-nullable field 'LocalPLayer' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 19 in Basis Server/BasisNetworkClientConsole/BasisNetworkClientConsole/Program.cs

View workflow job for this annotation

GitHub Actions / Build server on Ubuntu (Basis Server, win)

Non-nullable field 'LocalPLayer' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
public static void Main(string[] args)
{
// Set up global exception handlers
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

Check warning on line 24 in Basis Server/BasisNetworkClientConsole/BasisNetworkClientConsole/Program.cs

View workflow job for this annotation

GitHub Actions / Build server on Ubuntu (Basis Server, linux)

Nullability of reference types in type of parameter 'sender' of 'void Program.TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)' doesn't match the target delegate 'EventHandler<UnobservedTaskExceptionEventArgs>' (possibly because of nullability attributes).

Check warning on line 24 in Basis Server/BasisNetworkClientConsole/BasisNetworkClientConsole/Program.cs

View workflow job for this annotation

GitHub Actions / Build server on Ubuntu (Basis Server, win)

Nullability of reference types in type of parameter 'sender' of 'void Program.TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)' doesn't match the target delegate 'EventHandler<UnobservedTaskExceptionEventArgs>' (possibly because of nullability attributes).

// Get the path to the config.xml file in the application's directory
string configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.xml");


// Create a cancellation token source
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
Expand All @@ -25,32 +32,37 @@ public static void Main(string[] args)
{
try
{
// Generate random UUID and player name
string randomUUID = GenerateFakeUUID();
string randomPlayerName = GenerateRandomPlayerName();

ReadyMessage RM = new ReadyMessage
{
playerMetaDataMessage = new PlayerMetaDataMessage()
};
RM.playerMetaDataMessage.playerDisplayName = "Fake User";
RM.playerMetaDataMessage.playerUUID = "UUID Test";
RM.playerMetaDataMessage.playerDisplayName = randomPlayerName;
RM.playerMetaDataMessage.playerUUID = randomUUID;
RM.clientAvatarChangeMessage = new ClientAvatarChangeMessage
{
byteArray = new byte[13]
byteArray = new byte[0],
loadMode = 2
};
RM.localAvatarSyncMessage = new LocalAvatarSyncMessage
{
array = new byte[LocalAvatarSyncMessage.AvatarSyncSize]
array = AvatarMessage,
};
AuthenticationMessage Authmessage = new AuthenticationMessage
{
bytes = Encoding.UTF8.GetBytes("default_password")
bytes = Encoding.UTF8.GetBytes(Password)
};
BasisNetworkClient.AuthenticationMessage = Authmessage;
BasisNetworkClient.StartClient("localhost", 4296, RM,true);
BNL.Log("Connecting!");
LocalPLayer = BasisNetworkClient.StartClient("localhost", 4296, RM, true);
BasisNetworkClient.listener.NetworkReceiveEvent += NetworkReceiveEvent;
BNL.Log($"Connecting! Player Name: {randomPlayerName}, UUID: {randomUUID}");
}
catch (Exception ex)
{
BNL.LogError($"Server encountered an error: {ex.Message} {ex.StackTrace}");
// Optionally, handle server restart or log critical errors
}
}, cancellationToken);

Expand All @@ -72,14 +84,104 @@ public static void Main(string[] args)
}
BNL.Log("Server shut down successfully.");
};

ushort[] UshortArray = new ushort[LocalAvatarSyncMessage.StoredBones];
// Keep the application running
while (true)
{
Thread.Sleep(15000);
// SendMovement();
Thread.Sleep(33);
}
}

private static void NetworkReceiveEvent(NetPeer peer, NetPacketReader Reader, byte channel, DeliveryMethod deliveryMethod)
{

//loop back index 0 (0 being real player)
if (peer.Id == 0)
{
if (BasisNetworkCommons.MovementChannel == channel)
{
ServerSideSyncPlayerMessage SSM = new ServerSideSyncPlayerMessage();
SSM.Deserialize(Reader);
NetDataWriter Writer = NetDataWriterPool.GetWriter();
SSM.avatarSerialization.Serialize(Writer);
LocalPLayer.Send(Writer, BasisNetworkCommons.MovementChannel, deliveryMethod);
NetDataWriterPool.ReturnWriter(Writer);
}
else
{
if(BasisNetworkCommons.FallChannel == channel)
{
if (deliveryMethod == DeliveryMethod.Unreliable)
{
if (Reader.TryGetByte(out byte Byte))
{
NetworkReceiveEvent(peer, Reader, Byte, deliveryMethod);
}
else
{
BNL.LogError($"Unknown channel no data remains: {channel} " + Reader.AvailableBytes);
Reader.Recycle();
}
}
else
{
BNL.LogError($"Unknown channel: {channel} " + Reader.AvailableBytes);
Reader.Recycle();
}
}
}

}
}

public static byte[] AvatarMessage = new byte[LocalAvatarSyncMessage.AvatarSyncSize];
public static Vector3 Position = new Vector3(0,0,0);
public static Quaternion Rotation = new Quaternion(0,0,0,1);
public static float[] FloatArray = new float[LocalAvatarSyncMessage.StoredBones];
public static ushort[] UshortArray = new ushort[LocalAvatarSyncMessage.StoredBones];
public static void SendMovement()
{
if (LocalPLayer != null)
{
int Offset = 0;
WriteVectorFloatToBytes(Position, ref AvatarMessage, ref Offset);
WriteQuaternionToBytes(Rotation, ref AvatarMessage, ref Offset, RotationCompression);
WriteUShortsToBytes(UshortArray, ref AvatarMessage, ref Offset);
LocalPLayer.Send(AvatarMessage, BasisNetworkCommons.MovementChannel, DeliveryMethod.Sequenced);
}
}
public static ushort Compress(float value, float MinValue, float MaxValue, float valueDiffence)
{
// Clamp the value to ensure it's within the specified range
value = Math.Clamp(value, MinValue, MaxValue);

// Map the float value to the ushort range
float normalized = (value - MinValue) / (valueDiffence); // 0..1
return (ushort)(normalized * ushortRangeDifference);//+ UShortMin (its always zero)
}
private const ushort UShortMin = ushort.MinValue; // 0
private const ushort UShortMax = ushort.MaxValue; // 65535
private const ushort ushortRangeDifference = UShortMax - UShortMin;
public static BasisRangedUshortFloatData RotationCompression = new BasisRangedUshortFloatData(-1f, 1f, 0.001f);
public static void WriteUShortsToBytes(ushort[] values, ref byte[] bytes, ref int offset)
{
EnsureSize(ref bytes, offset + LengthUshortBytes);

// Manually copy ushort values as bytes
for (int index = 0; index < LocalAvatarSyncMessage.StoredBones; index++)
{
WriteUShortToBytes(values[index], ref bytes, ref offset);
}
}
// Manual ushort to bytes conversion (without BitConverter)
private unsafe static void WriteUShortToBytes(ushort value, ref byte[] bytes, ref int offset)
{
// Manually write the bytes
bytes[offset] = (byte)(value & 0xFF);
bytes[offset + 1] = (byte)((value >> 8) & 0xFF);
offset += 2;
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as Exception;
Expand All @@ -103,5 +205,138 @@ private static void TaskScheduler_UnobservedTaskException(object sender, Unobser
}
e.SetObserved(); // Prevents the application from crashing
}

private static string GenerateFakeUUID()
{
// Generate a fake UUID-like string
Guid guid = Guid.NewGuid();
return guid.ToString();
}

private static string GenerateRandomPlayerName()
{
// Thread-safe unique player name generation
lock (nameLock)
{
string[] adjectives = { "Swift", "Brave", "Clever", "Fierce", "Nimble", "Silent", "Bold", "Lucky", "Strong", "Mighty", "Sneaky", "Fearless", "Wise", "Vicious", "Daring" };
string[] nouns = { "Warrior", "Hunter", "Mage", "Rogue", "Paladin", "Shaman", "Knight", "Archer", "Monk", "Druid", "Assassin", "Sorcerer", "Ranger", "Guardian", "Berserker" };
string[] titles = { "the Swift", "the Bold", "the Silent", "the Brave", "the Fierce", "the Wise", "the Protector", "the Shadow", "the Flame", "the Phantom" };

// Colors with their corresponding names and hex codes for Unity's Rich Text
(string Name, string Hex)[] colors =
{
("Red", "#FF0000"),
("Blue", "#0000FF"),
("Green", "#008000"),
("Yellow", "#FFFF00"),
("Black", "#000000"),
("White", "#FFFFFF"),
("Silver", "#C0C0C0"),
("Golden", "#FFD700"),
("Crimson", "#DC143C"),
("Azure", "#007FFF"),
("Emerald", "#50C878"),
("Amber", "#FFBF00")
};

string[] animals = { "Wolf", "Tiger", "Eagle", "Dragon", "Lion", "Bear", "Hawk", "Panther", "Raven", "Serpent", "Fox", "Falcon" };

Random random = new Random();

// Randomly select one element from each array
string adjective = adjectives[random.Next(adjectives.Length)];
string noun = nouns[random.Next(nouns.Length)];
string title = titles[random.Next(titles.Length)];
var color = colors[random.Next(colors.Length)];
string animal = animals[random.Next(animals.Length)];

// Combine elements with rich text for the color
string colorText = $"<color={color.Hex}>{color.Name}</color>";
string generatedName = $"{adjective}{noun} {title} of the {colorText} {animal}";

// Ensure uniqueness by appending a counter
return $"{generatedName}";
}
}
public static void WriteVectorFloatToBytes(Vector3 values, ref byte[] bytes, ref int offset)
{
EnsureSize(ref bytes, offset + 12);
WriteFloatToBytes(values.x, ref bytes, ref offset);//4
WriteFloatToBytes(values.y, ref bytes, ref offset);//8
WriteFloatToBytes(values.z, ref bytes, ref offset);//12
}

private unsafe static void WriteFloatToBytes(float value, ref byte[] bytes, ref int offset)
{
// Convert the float to a uint using its bitwise representation
uint intValue = *((uint*)&value);

// Manually write the bytes
bytes[offset] = (byte)(intValue & 0xFF);
bytes[offset + 1] = (byte)((intValue >> 8) & 0xFF);
bytes[offset + 2] = (byte)((intValue >> 16) & 0xFF);
bytes[offset + 3] = (byte)((intValue >> 24) & 0xFF);
offset += 4;
}
public static int LengthUshortBytes = LocalAvatarSyncMessage.StoredBones * 2; // Initialize LengthBytes first
// Object pool for byte arrays to avoid allocation during runtime
private static readonly ObjectPool<byte[]> byteArrayPool = new ObjectPool<byte[]>(() => new byte[LengthUshortBytes]);
// Ensure the byte array is large enough to hold the data
private static void EnsureSize(ref byte[] bytes, int requiredSize)
{
if (bytes == null || bytes.Length < requiredSize)
{
// Reuse pooled byte arrays
bytes = byteArrayPool.Get();
Array.Resize(ref bytes, requiredSize);
}
}

// Ensure the byte array is large enough for reading
private static void EnsureSize(byte[] bytes, int requiredSize)
{
if (bytes.Length < requiredSize)
{
throw new ArgumentException("Byte array is too small for the required size. Current Size is " + bytes.Length + " But Required " + requiredSize);
}
}
// Manual conversion of quaternion to bytes (without BitConverter)
public static void WriteQuaternionToBytes(Quaternion rotation, ref byte[] bytes, ref int offset, BasisRangedUshortFloatData compressor)
{
EnsureSize(ref bytes, offset + 14);
ushort compressedW = compressor.Compress(rotation.value.w);

// Write the quaternion's components
WriteFloatToBytes(rotation.value.x, ref bytes, ref offset);
WriteFloatToBytes(rotation.value.y, ref bytes, ref offset);
WriteFloatToBytes(rotation.value.z, ref bytes, ref offset);

// Write the compressed 'w' component
bytes[offset] = (byte)(compressedW & 0xFF); // Low byte
bytes[offset + 1] = (byte)((compressedW >> 8) & 0xFF); // High byte
offset += 2;
}
// Object pool for byte arrays to avoid allocation during runtime
private class ObjectPool<T>
{
private readonly Func<T> createFunc;
private readonly Stack<T> pool;

public ObjectPool(Func<T> createFunc)
{
this.createFunc = createFunc;
this.pool = new Stack<T>();
}

public T Get()
{
return pool.Count > 0 ? pool.Pop() : createFunc();
}

public void Return(T item)
{
pool.Push(item);
}
}
}
}
Loading

0 comments on commit d6726cc

Please sign in to comment.