Use struct to serialize vehicle data

This gonna reduce the tedious work needed to add a new sync, also beings a performance boost
Ped, projectile sync will be updated later
This commit is contained in:
Sardelka9515 2023-03-26 15:36:15 +08:00
parent 826e80c5d8
commit 4fbdd86566
24 changed files with 445 additions and 550 deletions

View File

@ -321,37 +321,21 @@ namespace RageCoop.Client
private static void VehicleSync(Packets.VehicleSync packet)
{
var v = EntityPool.GetVehicleByID(packet.ID);
if (v == null) EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ID));
var v = EntityPool.GetVehicleByID(packet.ED.ID);
if (v == null) EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ED.ID));
if (v.IsLocal) return;
v.ID = packet.ID;
v.OwnerID = packet.OwnerID;
v.Flags = packet.Flags;
v.Position = packet.Position;
v.Quaternion = packet.Quaternion;
v.SteeringAngle = packet.SteeringAngle;
v.ThrottlePower = packet.ThrottlePower;
v.BrakePower = packet.BrakePower;
v.Velocity = packet.Velocity;
v.RotationVelocity = packet.RotationVelocity;
v.DeluxoWingRatio = packet.DeluxoWingRatio;
bool full = packet.Flags.HasVehFlag(VehicleDataFlags.IsFullSync);
v.ID = packet.ED.ID;
v.OwnerID = packet.ED.OwnerID;
v.Position = packet.ED.Position;
v.Quaternion = packet.ED.Quaternion;
v.Velocity = packet.ED.Velocity;
v.Model = packet.ED.ModelHash;
v.VD = packet.VD;
bool full = packet.VD.Flags.HasVehFlag(VehicleDataFlags.IsFullSync);
if (full)
{
v.DamageModel = packet.DamageModel;
v.EngineHealth = packet.EngineHealth;
v.Mods = packet.Mods;
v.ToggleModsMask = packet.ToggleModsMask;
v.Model = packet.ModelHash;
v.Colors = packet.Colors;
v.LandingGear = packet.LandingGear;
v.RoofState = (VehicleRoofState)packet.RoofState;
v.LockStatus = packet.LockStatus;
v.RadioStation = packet.RadioStation;
v.LicensePlate = packet.LicensePlate;
v.Livery = packet.Livery;
v.HeadlightColor = packet.HeadlightColor;
v.ExtrasMask = packet.ExtrasMask;
v.VDF = packet.VDF;
v.VDV = packet.VDV;
}
v.SetLastSynced(full);
}

View File

@ -103,19 +103,22 @@ namespace RageCoop.Client
if (v.LastSentStopWatch.ElapsedMilliseconds < SyncInterval) return;
var veh = v.MainVehicle;
var packet = SendPackets.VehicelPacket;
packet.ID = v.ID;
packet.OwnerID = v.OwnerID;
packet.Flags = v.GetVehicleFlags();
packet.SteeringAngle = veh.SteeringAngle;
packet.Position = veh.ReadPosition();
packet.Velocity = veh.Velocity;
packet.Quaternion = veh.ReadQuaternion();
packet.RotationVelocity = veh.WorldRotationVelocity;
packet.ThrottlePower = veh.ThrottlePower;
packet.BrakePower = veh.BrakePower;
packet.ED.ID = v.ID;
packet.ED.OwnerID = v.OwnerID;
packet.ED.Position = veh.ReadPosition();
packet.ED.Velocity = veh.Velocity;
packet.ED.Quaternion = veh.ReadQuaternion();
packet.ED.ModelHash = veh.Model.Hash;
packet.VD.Flags = v.GetVehicleFlags();
packet.VD.SteeringAngle = veh.SteeringAngle;
packet.VD.ThrottlePower = veh.ThrottlePower;
packet.VD.BrakePower = veh.BrakePower;
packet.VD.Flags |= VehicleDataFlags.IsFullSync;
packet.VD.LockStatus = veh.LockStatus;
v.LastSentStopWatch.Restart();
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
packet.DeluxoWingRatio = v.MainVehicle.GetDeluxoWingRatio();
if (packet.VD.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
packet.VD.DeluxoWingRatio = v.MainVehicle.GetDeluxoWingRatio();
if (full)
{
byte primaryColor = 0;
@ -125,23 +128,21 @@ namespace RageCoop.Client
Call<byte>(GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
}
packet.Flags |= VehicleDataFlags.IsFullSync;
packet.Colors = (primaryColor, secondaryColor);
packet.DamageModel = veh.GetVehicleDamageModel();
packet.LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0;
packet.RoofState = (byte)veh.RoofState;
packet.Mods = v.GetVehicleMods(out packet.ToggleModsMask);
packet.ModelHash = veh.Model.Hash;
packet.EngineHealth = veh.EngineHealth;
packet.LockStatus = veh.LockStatus;
packet.LicensePlate = Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
packet.Livery = Call<int>(GET_VEHICLE_LIVERY, veh);
packet.HeadlightColor = (byte)Call<int>(GET_VEHICLE_XENON_LIGHT_COLOR_INDEX, veh);
packet.ExtrasMask = v.GetVehicleExtras();
packet.RadioStation = v.MainVehicle == LastV
packet.VDF.LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0;
packet.VDF.RoofState = (byte)veh.RoofState;
packet.VDF.Colors = (primaryColor, secondaryColor);
packet.VDF.DamageModel = veh.GetVehicleDamageModel();
packet.VDF.EngineHealth = veh.EngineHealth;
packet.VDF.Livery = Call<int>(GET_VEHICLE_LIVERY, veh);
packet.VDF.HeadlightColor = (byte)Call<int>(GET_VEHICLE_XENON_LIGHT_COLOR_INDEX, veh);
packet.VDF.ExtrasMask = v.GetVehicleExtras();
packet.VDF.RadioStation = v.MainVehicle == LastV
? Util.GetPlayerRadioIndex() : byte.MaxValue;
if (packet.EngineHealth > v.LastEngineHealth) packet.Flags |= VehicleDataFlags.Repaired;
v.LastEngineHealth = packet.EngineHealth;
if (packet.VDF.EngineHealth > v.LastEngineHealth) packet.VD.Flags |= VehicleDataFlags.Repaired;
packet.VDV.Mods = v.GetVehicleMods(out packet.VDF.ToggleModsMask);
packet.VDV.LicensePlate = Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
v.LastEngineHealth = packet.VDF.EngineHealth;
}
SendSync(packet, ConnectionChannel.VehicleSync);

View File

@ -12,42 +12,23 @@ namespace RageCoop.Client
#region -- SYNC DATA --
internal Vector3 RotationVelocity { get; set; }
internal float SteeringAngle { get; set; }
internal float ThrottlePower { get; set; }
internal float BrakePower { get; set; }
internal float DeluxoWingRatio { get; set; } = -1;
internal byte LandingGear { get; set; }
internal VehicleRoofState RoofState { get; set; }
internal VehicleDamageModel DamageModel { get; set; }
internal (byte, byte) Colors { get; set; }
internal (int, int)[] Mods { get; set; }
internal float EngineHealth { get; set; }
internal VehicleLockStatus LockStatus { get; set; }
internal byte RadioStation = 255;
internal string LicensePlate { get; set; }
internal int Livery { get; set; } = -1;
internal byte HeadlightColor { get; set; } = 255;
internal VehicleDataFlags Flags { get; set; }
internal ushort ExtrasMask;
internal byte ToggleModsMask;
internal VehicleData VD;
internal VehicleDataFull VDF;
internal VehicleDataVar VDV;
#endregion
#region FLAGS
internal bool EngineRunning => Flags.HasVehFlag(VehicleDataFlags.IsEngineRunning);
internal bool Transformed => Flags.HasVehFlag(VehicleDataFlags.IsTransformed);
internal bool HornActive => Flags.HasVehFlag(VehicleDataFlags.IsHornActive);
internal bool LightsOn => Flags.HasVehFlag(VehicleDataFlags.AreLightsOn);
internal bool BrakeLightsOn => Flags.HasVehFlag(VehicleDataFlags.AreBrakeLightsOn);
internal bool HighBeamsOn => Flags.HasVehFlag(VehicleDataFlags.AreHighBeamsOn);
internal bool SireneActive => Flags.HasVehFlag(VehicleDataFlags.IsSirenActive);
internal bool IsDead => Flags.HasVehFlag(VehicleDataFlags.IsDead);
internal bool IsDeluxoHovering => Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering);
internal bool EngineRunning => VD.Flags.HasVehFlag(VehicleDataFlags.IsEngineRunning);
internal bool Transformed => VD.Flags.HasVehFlag(VehicleDataFlags.IsTransformed);
internal bool HornActive => VD.Flags.HasVehFlag(VehicleDataFlags.IsHornActive);
internal bool LightsOn => VD.Flags.HasVehFlag(VehicleDataFlags.AreLightsOn);
internal bool BrakeLightsOn => VD.Flags.HasVehFlag(VehicleDataFlags.AreBrakeLightsOn);
internal bool HighBeamsOn => VD.Flags.HasVehFlag(VehicleDataFlags.AreHighBeamsOn);
internal bool SireneActive => VD.Flags.HasVehFlag(VehicleDataFlags.IsSirenActive);
internal bool IsDead => VD.Flags.HasVehFlag(VehicleDataFlags.IsDead);
internal bool IsDeluxoHovering => VD.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering);
#endregion
@ -69,15 +50,13 @@ namespace RageCoop.Client
#endregion
#region PRIVATE
private byte _lastToggleMods;
private (byte, byte) _lastVehicleColors;
private ushort _lastExtras;
private (int, int)[] _lastVehicleMods = Array.Empty<(int, int)>();
private bool _lastHornActive;
private bool _lastTransformed;
private int _lastLivery = -1;
private byte _lastHeadlightColor = 255;
private VehicleData _lastVD;
private VehicleDataFull _lastVDF;
private VehicleDataVar _lastVDV;
private Vector3 _predictedPosition;
internal bool _lastTransformed => _lastVD.Flags.HasVehFlag(VehicleDataFlags.IsTransformed);
internal bool _lastHornActive => _lastVD.Flags.HasVehFlag(VehicleDataFlags.IsHornActive);
#endregion
#region OUTGOING

View File

@ -29,10 +29,10 @@ namespace RageCoop.Client
// Skip update if no new sync message has arrived.
if (!NeedUpdate) return;
if (SteeringAngle != MainVehicle.SteeringAngle)
MainVehicle.CustomSteeringAngle((float)(Math.PI / 180) * SteeringAngle);
MainVehicle.ThrottlePower = ThrottlePower;
MainVehicle.BrakePower = BrakePower;
if (VD.SteeringAngle != MainVehicle.SteeringAngle)
MainVehicle.CustomSteeringAngle((float)(Math.PI / 180) * VD.SteeringAngle);
MainVehicle.ThrottlePower = VD.ThrottlePower;
MainVehicle.BrakePower = VD.BrakePower;
if (IsDead)
{
@ -51,9 +51,9 @@ namespace RageCoop.Client
if (MainVehicle.IsOnFire)
{
if (!Flags.HasVehFlag(VehicleDataFlags.IsOnFire)) Call(STOP_ENTITY_FIRE, MainVehicle);
if (!VD.Flags.HasVehFlag(VehicleDataFlags.IsOnFire)) Call(STOP_ENTITY_FIRE, MainVehicle);
}
else if (Flags.HasVehFlag(VehicleDataFlags.IsOnFire))
else if (VD.Flags.HasVehFlag(VehicleDataFlags.IsOnFire))
{
Call(START_ENTITY_FIRE, MainVehicle);
}
@ -64,12 +64,7 @@ namespace RageCoop.Client
if (HighBeamsOn != MainVehicle.AreHighBeamsOn) MainVehicle.AreHighBeamsOn = HighBeamsOn;
if (IsAircraft)
{
if (LandingGear != (byte)MainVehicle.LandingGearState)
MainVehicle.LandingGearState = (VehicleLandingGearState)LandingGear;
}
else
if (!IsAircraft)
{
if (MainVehicle.HasSiren && SireneActive != MainVehicle.IsSirenActive)
MainVehicle.IsSirenActive = SireneActive;
@ -78,22 +73,18 @@ namespace RageCoop.Client
{
if (!_lastHornActive)
{
_lastHornActive = true;
MainVehicle.SoundHorn(99999);
}
}
else if (_lastHornActive)
else if (_lastVD.Flags.HasVehFlag(VehicleDataFlags.IsHornActive))
{
_lastHornActive = false;
MainVehicle.SoundHorn(1);
}
if (HasRoof && MainVehicle.RoofState != RoofState) MainVehicle.RoofState = RoofState;
if (HasRocketBoost && Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive) !=
if (HasRocketBoost && VD.Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive) !=
MainVehicle.IsRocketBoostActive)
MainVehicle.IsRocketBoostActive = Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive);
if (HasParachute && Flags.HasFlag(VehicleDataFlags.IsParachuteActive) &&
MainVehicle.IsRocketBoostActive = VD.Flags.HasVehFlag(VehicleDataFlags.IsRocketBoostActive);
if (HasParachute && VD.Flags.HasFlag(VehicleDataFlags.IsParachuteActive) &&
!MainVehicle.IsParachuteDeployed)
MainVehicle.StartParachuting(false);
if (IsSubmarineCar)
@ -102,75 +93,68 @@ namespace RageCoop.Client
{
if (!_lastTransformed)
{
_lastTransformed = true;
Call(TRANSFORM_TO_SUBMARINE, MainVehicle.Handle, false);
}
}
else if (_lastTransformed)
{
_lastTransformed = false;
Call(TRANSFORM_TO_CAR, MainVehicle.Handle, false);
}
}
else if (IsDeluxo)
{
MainVehicle.SetDeluxoHoverState(IsDeluxoHovering);
if (IsDeluxoHovering) MainVehicle.SetDeluxoWingRatio(DeluxoWingRatio);
if (IsDeluxoHovering) MainVehicle.SetDeluxoWingRatio(VD.DeluxoWingRatio);
}
Call(SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
MainVehicle.LockStatus = VD.LockStatus;
}
MainVehicle.LockStatus = LockStatus;
_lastVD = VD;
if (LastFullSynced >= LastUpdated)
{
if (Flags.HasVehFlag(VehicleDataFlags.Repaired)) MainVehicle.Repair();
if (Colors != _lastVehicleColors)
if (IsAircraft)
{
Call(SET_VEHICLE_COLOURS, MainVehicle, Colors.Item1, Colors.Item2);
_lastVehicleColors = Colors;
if (VDF.LandingGear != (byte)MainVehicle.LandingGearState)
MainVehicle.LandingGearState = (VehicleLandingGearState)VDF.LandingGear;
}
MainVehicle.EngineHealth = EngineHealth;
if (Mods != null && !Mods.SequenceEqual(_lastVehicleMods))
if (HasRoof && MainVehicle.RoofState != (VehicleRoofState)VDF.RoofState)
MainVehicle.RoofState = (VehicleRoofState)VDF.RoofState;
if (VD.Flags.HasVehFlag(VehicleDataFlags.Repaired)) MainVehicle.Repair();
if (VDF.Colors != _lastVDF.Colors)
{
Call(SET_VEHICLE_MOD_KIT, MainVehicle, 0);
foreach (var mod in Mods) MainVehicle.Mods[(VehicleModType)mod.Item1].Index = mod.Item2;
_lastVehicleMods = Mods;
Call(SET_VEHICLE_COLOURS, MainVehicle, VDF.Colors.Item1, VDF.Colors.Item2);
}
if (ToggleModsMask != _lastToggleMods)
MainVehicle.EngineHealth = VDF.EngineHealth;
if (VDF.ToggleModsMask != _lastVDF.ToggleModsMask)
{
for (int i = 0; i < 7; i++)
{
Call(TOGGLE_VEHICLE_MOD, MainVehicle.Handle, i + 17, (ToggleModsMask & (1 << i)) != 0);
Call(TOGGLE_VEHICLE_MOD, MainVehicle.Handle, i + 17, (VDF.ToggleModsMask & (1 << i)) != 0);
}
_lastToggleMods = ToggleModsMask;
}
if (Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle) != LicensePlate)
Call(SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, LicensePlate);
if (_lastLivery != Livery)
if (VDF.Livery != _lastVDF.Livery)
{
Call(SET_VEHICLE_LIVERY, MainVehicle, Livery);
_lastLivery = Livery;
Call(SET_VEHICLE_LIVERY, MainVehicle, VDF.Livery);
}
if (_lastHeadlightColor != HeadlightColor)
if (VDF.HeadlightColor != _lastVDF.HeadlightColor)
{
Call(SET_VEHICLE_XENON_LIGHT_COLOR_INDEX, MainVehicle.Handle, HeadlightColor);
_lastHeadlightColor = HeadlightColor;
Call(SET_VEHICLE_XENON_LIGHT_COLOR_INDEX, MainVehicle.Handle, VDF.HeadlightColor);
}
MainVehicle.SetDamageModel(DamageModel);
if (MainVehicle.Handle == V?.Handle && Util.GetPlayerRadioIndex() != RadioStation)
Util.SetPlayerRadioIndex(MainVehicle.Handle, RadioStation);
if (!CoreUtils.StructCmp(VDF.DamageModel, _lastVDF.DamageModel))
{
MainVehicle.SetDamageModel(VDF.DamageModel);
}
if (_lastExtras != ExtrasMask)
if (MainVehicle.Handle == V?.Handle && Util.GetPlayerRadioIndex() != VDF.RadioStation)
Util.SetPlayerRadioIndex(MainVehicle.Handle, VDF.RadioStation);
if (VDF.ExtrasMask != _lastVDF.ExtrasMask)
{
for (int i = 1; i < 15; i++)
{
@ -179,11 +163,21 @@ namespace RageCoop.Client
if (!hasExtra)
continue;
var on = (ExtrasMask & flag) != 0;
var on = (VDF.ExtrasMask & flag) != 0;
Call(SET_VEHICLE_EXTRA, MainVehicle.Handle, i, !on);
}
_lastExtras = ExtrasMask;
}
if (VDV.Mods != null && (_lastVDV.Mods == null || !VDV.Mods.SequenceEqual(_lastVDV.Mods)))
{
Call(SET_VEHICLE_MOD_KIT, MainVehicle, 0);
foreach (var mod in VDV.Mods) MainVehicle.Mods[(VehicleModType)mod.Item1].Index = mod.Item2;
}
if (VDV.LicensePlate != _lastVDV.LicensePlate)
Call(SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, VDV.LicensePlate);
_lastVDF = VDF;
_lastVDV = VDV;
}
LastUpdated = Ticked;
@ -240,7 +234,7 @@ namespace RageCoop.Client
}
MainVehicle.Quaternion = Quaternion;
if (MainVehicle.HasRoof) MainVehicle.RoofState = RoofState;
if (MainVehicle.HasRoof) MainVehicle.RoofState = (VehicleRoofState)VDF.RoofState;
foreach (var w in MainVehicle.Wheels) w.Fix();
if (IsInvincible) MainVehicle.IsInvincible = true;
SetUpFixedData();
@ -248,6 +242,8 @@ namespace RageCoop.Client
return true;
}
#region -- CONSTRUCTORS --
/// <summary>

View File

@ -31,8 +31,8 @@ namespace RageCoop.Client
protected override void OnStart()
{
base.OnStart();
while(Game.IsLoading)
Yield();
while (Game.IsLoading)
Yield();
Notification.Show(NotificationIcon.AllPlayersConf, "RAGECOOP", "Welcome!",
$"Press ~g~{Settings.MenuKey}~s~ to open the menu.");
@ -41,6 +41,18 @@ namespace RageCoop.Client
{
base.OnTick();
if (_sleeping)
{
Game.Pause(true);
while (_sleeping)
{
// Don't wait longer than 5 seconds or the game will crash
Thread.Sleep(4500);
Yield();
}
Game.Pause(false);
}
if (Game.IsLoading) return;
try
@ -224,5 +236,21 @@ namespace RageCoop.Client
QueuedActions.Clear();
}
}
private static bool _sleeping;
[ConsoleCommand("Put the game to sleep state by blocking main thread, press any key in the debug console to resume")]
public static void Sleep()
{
if (_sleeping)
throw new InvalidOperationException("Already in sleep state");
_sleeping = true;
Task.Run(() =>
{
System.Console.WriteLine("Press any key to put the game out of sleep state");
System.Console.ReadKey();
System.Console.WriteLine("Game resumed");
_sleeping = false;
});
}
}
}

View File

@ -113,32 +113,6 @@ namespace RageCoop.Core
Encoding.UTF8.GetBytes(str, new(pBody, cbBody));
}
// Struct in GTA.Math have pack/padding for memory alignment, we don't want it to waste the bandwidth
public void Write(ref Vector2 vec2)
{
var faddr = Alloc<float>(2);
faddr[0] = vec2.X;
faddr[1] = vec2.Y;
}
public void Write(ref Vector3 vec3)
{
var faddr = Alloc<float>(3);
faddr[0] = vec3.X;
faddr[1] = vec3.Y;
faddr[2] = vec3.Z;
}
public void Write(ref Quaternion quat)
{
var faddr = Alloc<float>(4);
faddr[0] = quat.X;
faddr[1] = quat.Y;
faddr[2] = quat.Z;
faddr[3] = quat.W;
}
public void Write<T>(ReadOnlySpan<T> source) where T : unmanaged
{
var len = source.Length;
@ -237,39 +211,6 @@ namespace RageCoop.Core
str = Encoding.UTF8.GetString(Alloc(cbBody), cbBody);
}
public void Read(out Vector2 vec)
{
var faddr = Alloc<float>(2);
vec = new()
{
X = faddr[0],
Y = faddr[1],
};
}
public void Read(out Vector3 vec)
{
var faddr = Alloc<float>(3);
vec = new()
{
X = faddr[0],
Y = faddr[1],
Z = faddr[2],
};
}
public void Read(out Quaternion quat)
{
var faddr = Alloc<float>(4);
quat = new()
{
X = faddr[0],
Y = faddr[1],
Z = faddr[2],
W = faddr[3],
};
}
/// <summary>
/// Read a span of type <typeparamref name="T"/> from current position to <paramref name="destination"/>
/// </summary>

22
Core/CompactVectors.cs Normal file
View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GTA.Math;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
internal struct LQuaternion
{
public LQuaternion(float x, float y, float z, float w)
{
X = x; Y = y; Z = z; W = w;
}
public float X, Y, Z, W;
public static implicit operator LQuaternion(Quaternion q) => new(q.X, q.Y, q.Z, q.W);
public static implicit operator Quaternion(LQuaternion q) => new(q.X, q.Y, q.Z, q.W);
}
}

View File

@ -8,8 +8,7 @@ using GTA.Math;
namespace RageCoop.Core.CompactVectors
{
public struct LQuaternion : IEquatable<LQuaternion>
internal struct LQuaternion : IEquatable<LQuaternion>
{
/// <summary>
/// Gets or sets the X component of the quaternion.

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace RageCoop.Core
{
public struct LVector2 : IEquatable<LVector2>
internal struct LVector2 : IEquatable<LVector2>
{
/// <summary>
/// Gets or sets the X component of the vector.

View File

@ -24,7 +24,7 @@ namespace RageCoop.Core
{
[Serializable]
public struct LVector3 : IEquatable<LVector3>
internal struct LVector3 : IEquatable<LVector3>
{
/// <summary>
/// Gets or sets the X component of the vector.

View File

@ -31,7 +31,10 @@ namespace RageCoop.Core
{
internal static class CoreUtils
{
private static readonly Random random = new();
internal static Random SafeRandom => _randInstance.Value;
private static int _randSeed = Environment.TickCount;
private static readonly ThreadLocal<Random> _randInstance
= new(() => new Random(Interlocked.Increment(ref _randSeed)));
private static readonly HashSet<string> ToIgnore = new()
{
@ -72,7 +75,7 @@ namespace RageCoop.Core
public static int RandInt(int start, int end)
{
return random.Next(start, end);
return SafeRandom.Next(start, end);
}
public static string GetTempDirectory(string dir = null)
@ -91,7 +94,7 @@ namespace RageCoop.Core
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
.Select(s => s[SafeRandom.Next(s.Length)]).ToArray());
}
public static Version GetLatestVersion(string branch = "dev-nightly")
@ -139,79 +142,6 @@ namespace RageCoop.Core
return Path.GetFullPath(path);
}
public static void GetBytesFromObject(object obj, NetOutgoingMessage m)
{
switch (obj)
{
case byte value:
m.Write((byte)0x01);
m.Write(value);
break;
case short value:
m.Write((byte)0x02);
m.Write(value);
break;
case ushort value:
m.Write((byte)0x03);
m.Write(value);
break;
case int value:
m.Write((byte)0x04);
m.Write(value);
break;
case uint value:
m.Write((byte)0x05);
m.Write(value);
break;
case long value:
m.Write((byte)0x06);
m.Write(value);
break;
case ulong value:
m.Write((byte)0x07);
m.Write(value);
break;
case float value:
m.Write((byte)0x08);
m.Write(value);
break;
case bool value:
m.Write((byte)0x09);
m.Write(value);
break;
case string value:
m.Write((byte)0x10);
m.Write(value);
break;
case Vector3 value:
m.Write((byte)0x11);
m.Write(value);
break;
case Quaternion value:
m.Write((byte)0x12);
m.Write(value);
break;
case Model value:
m.Write((byte)0x13);
m.Write(value);
break;
case Vector2 value:
m.Write((byte)0x14);
m.Write(value);
break;
case byte[] value:
m.Write((byte)0x15);
m.WriteByteArray(value);
break;
case Tuple<byte, byte[]> value:
m.Write(value.Item1);
m.Write(value.Item2);
break;
default:
throw new Exception("Unsupported object type: " + obj.GetType());
}
}
public static IPEndPoint StringToEndPoint(string endpointstring)
{
return StringToEndPoint(endpointstring, -1);
@ -386,6 +316,65 @@ namespace RageCoop.Core
hash += hash << 15;
return hash;
}
public static unsafe bool StructCmp<T>(ref T left, ref T right) where T : unmanaged
{
fixed (T* pLeft = &left, pRight = &right)
{
return MemCmp(pLeft, pRight, sizeof(T));
}
}
public static unsafe bool StructCmp<T>(T left, T right) where T : unmanaged
{
return MemCmp(&left, &right, sizeof(T));
}
static bool _simdSupported = System.Numerics.Vector<byte>.IsSupported;
static int _simdSlots = _simdSupported ? System.Numerics.Vector<byte>.Count : 0;
/// <summary>
/// SIMD-accelerated memory comparer
/// </summary>
/// <returns></returns>
public static unsafe bool MemCmp(void* p1, void* p2, int cbToCompare)
{
int numVectors = cbToCompare / _simdSlots;
int ceiling = numVectors * _simdSlots;
if (numVectors > 0)
{
ReadOnlySpan<System.Numerics.Vector<byte>> leftVecArray = new(p1, numVectors);
ReadOnlySpan<System.Numerics.Vector<byte>> rightVecArray = new(p2, numVectors);
for (int i = 0; i < numVectors; i++)
{
if (leftVecArray[i] != rightVecArray[i])
return false;
}
}
int numWords = cbToCompare / sizeof(IntPtr);
var pwLeft = (IntPtr*)p1;
var pwRight = (IntPtr*)p2;
for (int i = (ceiling / sizeof(IntPtr)); i < numWords; i++)
{
if (pwLeft[i] != pwRight[i])
return false;
}
var pbLeft = (byte*)p1;
var pbRight = (byte*)p2;
for (int i = ceiling + (numWords * sizeof(IntPtr)); i < cbToCompare; i++)
{
if (pbLeft[i] != pbRight[i])
return false;
}
return true;
}
}
internal class IpInfo
@ -407,33 +396,6 @@ namespace RageCoop.Core
return Encoding.UTF8.GetString(data);
}
public static byte[] GetBytes(this Vector3 vec)
{
// 12 bytes
return new List<byte[]>
{ BitConverter.GetBytes(vec.X), BitConverter.GetBytes(vec.Y), BitConverter.GetBytes(vec.Z) }.Join(4);
}
public static byte[] GetBytes(this Vector2 vec)
{
// 8 bytes
return new List<byte[]> { BitConverter.GetBytes(vec.X), BitConverter.GetBytes(vec.Y) }.Join(4);
}
/// <summary>
/// </summary>
/// <param name="qua"></param>
/// <returns>An array of bytes with length 16</returns>
public static byte[] GetBytes(this Quaternion qua)
{
// 16 bytes
return new List<byte[]>
{
BitConverter.GetBytes(qua.X), BitConverter.GetBytes(qua.Y), BitConverter.GetBytes(qua.Z),
BitConverter.GetBytes(qua.W)
}.Join(4);
}
public static T GetPacket<T>(this NetIncomingMessage msg) where T : Packet, new()
{
var p = new T();

View File

@ -12,9 +12,9 @@ namespace RageCoop.Core
#region MESSAGE-READ
public static Vector3 ReadVector3(this NetIncomingMessage m)
public static LVector3 ReadVector3(this NetIncomingMessage m)
{
return new Vector3
return new LVector3
{
X = m.ReadFloat(),
Y = m.ReadFloat(),
@ -22,9 +22,9 @@ namespace RageCoop.Core
};
}
public static Vector2 ReadVector2(this NetIncomingMessage m)
public static LVector2 ReadVector2(this NetIncomingMessage m)
{
return new Vector2
return new LVector2
{
X = m.ReadFloat(),
Y = m.ReadFloat()
@ -51,7 +51,7 @@ namespace RageCoop.Core
#region MESSAGE-WRITE
public static void Write(this NetOutgoingMessage m, Vector3 v)
public static void Write(this NetOutgoingMessage m, LVector3 v)
{
m.Write(v.X);
m.Write(v.Y);

View File

@ -0,0 +1,74 @@
using GTA;
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
/// <summary>
/// Common data for synchronizing an entity
/// </summary>
internal struct EntityData
{
public int ID;
public int OwnerID;
public LQuaternion Quaternion;
public LVector3 Position;
public LVector3 Velocity;
public int ModelHash;
}
internal struct VehicleData
{
public VehicleDataFlags Flags;
public float ThrottlePower;
public float BrakePower;
public float SteeringAngle;
public VehicleLockStatus LockStatus;
public float DeluxoWingRatio;
}
internal struct VehicleDataFull
{
public float EngineHealth;
public (byte, byte) Colors;
public byte ToggleModsMask;
public VehicleDamageModel DamageModel;
public int Livery;
public byte HeadlightColor;
public byte RadioStation;
public ushort ExtrasMask;
public byte RoofState;
public byte LandingGear;
}
/// <summary>
/// Non-fixed vehicle data
/// </summary>
internal struct VehicleDataVar
{
public string LicensePlate;
public (int, int)[] Mods;
public void WriteTo(NetOutgoingMessage m)
{
m.Write(LicensePlate);
m.Write((byte)Mods.Length);
for(int i = 0;i < Mods.Length; i++)
{
m.Write(ref Mods[i]);
}
}
public void ReadFrom(NetIncomingMessage m)
{
LicensePlate = m.ReadString();
Mods = new (int, int)[m.ReadByte()];
for(int i = 0; i < Mods.Length; i++)
{
Mods[i] = m.Read<(int, int)>();
}
}
}
}

View File

@ -20,15 +20,15 @@ namespace RageCoop.Core
public int Health { get; set; }
public Vector3 Position { get; set; }
public LVector3 Position { get; set; }
public Vector3 Rotation { get; set; }
public LVector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
public LVector3 Velocity { get; set; }
public byte Speed { get; set; }
public Vector3 AimCoords { get; set; }
public LVector3 AimCoords { get; set; }
public float Heading { get; set; }
@ -178,9 +178,9 @@ namespace RageCoop.Core
#region RAGDOLL
public Vector3 HeadPosition { get; set; }
public Vector3 RightFootPosition { get; set; }
public Vector3 LeftFootPosition { get; set; }
public LVector3 HeadPosition { get; set; }
public LVector3 RightFootPosition { get; set; }
public LVector3 LeftFootPosition { get; set; }
#endregion

View File

@ -176,7 +176,7 @@ namespace RageCoop.Core
public string Username { get; set; }
public float Latency { get; set; }
public Vector3 Position { get; set; }
public LVector3 Position { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{

View File

@ -13,11 +13,11 @@ namespace RageCoop.Core
public int ShooterID { get; set; }
public uint WeaponHash { get; set; }
public Vector3 Position { get; set; }
public LVector3 Position { get; set; }
public Vector3 Rotation { get; set; }
public LVector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
public LVector3 Velocity { get; set; }
public ProjectileDataFlags Flags { get; set; }

View File

@ -12,7 +12,7 @@ namespace RageCoop.Core
public uint WeaponHash { get; set; }
public Vector3 EndPosition { get; set; }
public LVector3 EndPosition { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{

View File

@ -10,208 +10,32 @@ namespace RageCoop.Core
public class VehicleSync : Packet
{
public override PacketType Type => PacketType.VehicleSync;
public int ID { get; set; }
public int OwnerID { get; set; }
public VehicleDataFlags Flags { get; set; }
public Vector3 Position { get; set; }
public Quaternion Quaternion { get; set; }
// public Vector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
public Vector3 RotationVelocity { get; set; }
public float ThrottlePower { get; set; }
public float BrakePower { get; set; }
public float SteeringAngle { get; set; }
public float DeluxoWingRatio { get; set; } = -1;
public EntityData ED;
public VehicleData VD;
public VehicleDataFull VDF;
public VehicleDataVar VDV;
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(ID);
m.Write(OwnerID);
m.Write((ushort)Flags);
m.Write(Position);
m.Write(Quaternion);
m.Write(Velocity);
m.Write(RotationVelocity);
m.Write(ThrottlePower);
m.Write(BrakePower);
m.Write(SteeringAngle);
if (Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering)) m.Write(DeluxoWingRatio);
if (Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
m.Write(ref ED);
m.Write(ref VD);
if (VD.Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
m.Write(ModelHash);
m.Write(EngineHealth);
// Check
if (Flags.HasVehFlag(VehicleDataFlags.IsAircraft))
// Write the vehicle landing gear
m.Write(LandingGear);
if (Flags.HasVehFlag(VehicleDataFlags.HasRoof)) m.Write(RoofState);
// Write vehicle colors
m.Write(Colors.Item1);
m.Write(Colors.Item2);
// Write vehicle mods
// Write the count of mods
m.Write((short)Mods.Length);
foreach (var mod in Mods)
{
// Write the mod value
m.Write(mod.Item1);
m.Write(mod.Item2);
}
m.Write(ToggleModsMask);
if (!DamageModel.Equals(default(VehicleDamageModel)))
{
// Write boolean = true
m.Write(true);
// Write vehicle damage model
m.Write(DamageModel.BrokenDoors);
m.Write(DamageModel.OpenedDoors);
m.Write(DamageModel.BrokenWindows);
m.Write(DamageModel.BurstedTires);
m.Write(DamageModel.LeftHeadLightBroken);
m.Write(DamageModel.RightHeadLightBroken);
}
else
{
// Write boolean = false
m.Write(false);
}
// Write LockStatus
m.Write((byte)LockStatus);
// Write RadioStation
m.Write(RadioStation);
// Write LicensePlate
m.Write(LicensePlate);
m.Write((byte)(Livery + 1));
m.Write(HeadlightColor);
m.Write(ExtrasMask);
m.Write(ref VDF);
VDV.WriteTo(m);
}
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
ID = m.ReadInt32();
OwnerID = m.ReadInt32();
Flags = (VehicleDataFlags)m.ReadUInt16();
Position = m.ReadVector3();
Quaternion = m.ReadQuaternion();
Velocity = m.ReadVector3();
RotationVelocity = m.ReadVector3();
ThrottlePower = m.ReadFloat();
BrakePower = m.ReadFloat();
SteeringAngle = m.ReadFloat();
if (Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering)) DeluxoWingRatio = m.ReadFloat();
if (Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
m.Read(out ED);
m.Read(out VD);
if (VD.Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
// Read vehicle model hash
ModelHash = m.ReadInt32();
// Read vehicle engine health
EngineHealth = m.ReadFloat();
// Check
if (Flags.HasVehFlag(VehicleDataFlags.IsAircraft))
// Read vehicle landing gear
LandingGear = m.ReadByte();
if (Flags.HasVehFlag(VehicleDataFlags.HasRoof)) RoofState = m.ReadByte();
// Read vehicle colors
Colors = (m.ReadByte(), m.ReadByte());
// Read vehicle mods
// Create new Dictionary
// Read count of mods
var vehModCount = m.ReadInt16();
Mods = new (int, int)[vehModCount];
// Loop
for (var i = 0; i < vehModCount; i++)
// Read the mod value
Mods[i] = (m.ReadInt32(), m.ReadInt32());
ToggleModsMask = m.ReadByte();
if (m.ReadBoolean())
// Read vehicle damage model
DamageModel = new VehicleDamageModel
{
BrokenDoors = m.ReadByte(),
OpenedDoors = m.ReadByte(),
BrokenWindows = m.ReadByte(),
BurstedTires = m.ReadInt16(),
LeftHeadLightBroken = m.ReadByte(),
RightHeadLightBroken = m.ReadByte()
};
// Read LockStatus
LockStatus = (VehicleLockStatus)m.ReadByte();
// Read RadioStation
RadioStation = m.ReadByte();
LicensePlate = m.ReadString();
Livery = m.ReadByte() - 1;
HeadlightColor = m.ReadByte();
ExtrasMask = m.ReadUInt16();
m.Read(out VDF);
VDV.ReadFrom(m);
}
#endregion
}
#region FULL-SYNC
public int ModelHash { get; set; }
public float EngineHealth { get; set; }
public (byte, byte) Colors { get; set; }
public (int, int)[] Mods { get; set; }
public byte ToggleModsMask;
public VehicleDamageModel DamageModel { get; set; }
public byte LandingGear { get; set; }
public byte RoofState { get; set; }
public VehicleLockStatus LockStatus { get; set; }
public int Livery { get; set; } = -1;
public byte HeadlightColor { get; set; } = 255;
public byte RadioStation { get; set; } = 255;
public string LicensePlate { get; set; }
public ushort ExtrasMask;
#endregion
}
}
}

View File

@ -193,14 +193,29 @@ namespace RageCoop.Core.Scripting
b.Write(value);
break;
case Vector2 value:
b.WriteVal(T_VEC2);
var vec2 = (LVector2)value;
b.Write(ref vec2);
break;
case LVector2 value:
b.WriteVal(T_VEC2);
b.Write(ref value);
break;
case Vector3 value:
b.WriteVal(T_VEC3);
var vec3 = (LVector3)value;
b.Write(ref vec3);
break;
case LVector3 value:
b.WriteVal(T_VEC3);
b.Write(ref value);
break;
case Quaternion value:
b.WriteVal(T_QUAT);
var quat = (LQuaternion)value;
b.Write(ref quat);
break;
case LQuaternion value:
b.WriteVal(T_QUAT);
b.Write(ref value);
break;
@ -261,19 +276,19 @@ namespace RageCoop.Core.Scripting
Args[i] = str;
break;
case T_VEC3:
r.Read(out Vector3 vec);
Args[i] = vec;
r.Read(out LVector3 vec);
Args[i] = (Vector3)vec;
break;
case T_QUAT:
r.Read(out Quaternion quat);
Args[i] = quat;
r.Read(out LQuaternion quat);
Args[i] = (Quaternion)quat;
break;
case T_MODEL:
Args[i] = r.ReadVal<Model>();
break;
case T_VEC2:
r.Read(out Vector2 vec2);
Args[i] = vec2;
r.Read(out LVector2 vec2);
Args[i] = (Vector2)vec2;
break;
case T_BYTEARR:
Args[i] = r.ReadArray<byte>();

View File

@ -40,7 +40,7 @@ public partial class Server
private void VehicleSync(Packets.VehicleSync packet, Client client)
{
QueueJob(() => Entities.Update(packet, client));
var isPlayer = packet.ID == client.Player?.LastVehicle?.ID;
var isPlayer = packet.ED.ID == client.Player?.LastVehicle?.ID;
if (Settings.UseP2P) return;
@ -51,10 +51,10 @@ public partial class Server
{
// Player's vehicle
if (Settings.PlayerStreamingDistance != -1 &&
packet.Position.DistanceTo(c.Player.Position) > Settings.PlayerStreamingDistance) continue;
packet.ED.Position.DistanceTo(c.Player.Position) > Settings.PlayerStreamingDistance) continue;
}
else if (Settings.NpcStreamingDistance != -1 &&
packet.Position.DistanceTo(c.Player.Position) > Settings.NpcStreamingDistance)
packet.ED.Position.DistanceTo(c.Player.Position) > Settings.NpcStreamingDistance)
{
continue;
}

View File

@ -194,14 +194,14 @@ public class ServerEntities
internal void Update(Packets.VehicleSync p, Client sender)
{
if (!Vehicles.TryGetValue(p.ID, out var veh))
if (!Vehicles.TryGetValue(p.ED.ID, out var veh))
{
Vehicles.TryAdd(p.ID, veh = new ServerVehicle(Server));
veh.ID = p.ID;
Vehicles.TryAdd(p.ED.ID, veh = new ServerVehicle(Server));
veh.ID = p.ED.ID;
}
veh._pos = p.Position + p.Velocity * sender.Latency;
veh._quat = p.Quaternion;
veh._pos = p.ED.Position + p.ED.Velocity * sender.Latency;
veh._quat = p.ED.Quaternion;
if (veh.Owner != sender)
{
if (veh.Owner != null) veh.Owner.EntitiesCount--;

View File

@ -1,6 +1,10 @@
using GTA.Math;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using GTA.Math;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
namespace UnitTest
@ -23,10 +27,16 @@ namespace UnitTest
public Quaternion quat;
public string str;
}
internal unsafe class Program
struct TestStruct
{
public ulong val1;
public ulong val2;
}
public unsafe partial class Program
{
static void Main(string[] args)
{
TestElement[] test = new TestElement[1024];
Console.WriteLine("Testing buffers");
var buf = new BufferWriter(1024);
@ -72,21 +82,76 @@ namespace UnitTest
Console.WriteLine("Buffers OK");
Console.WriteLine("Testing CustomEvents");
var objs = new object[] { (byte)236, (short)82, (ushort)322,
var objs = new object[] { (byte)236, (short)82, (ushort)322,
"test", 123, 123U, 456UL, 345L, 5F, new Vector2(15, 54), new Vector3(22, 45, 25), new Quaternion(2, 3, 222, 5) };
buf.Reset();
CustomEvents.WriteObjects(buf, objs);
var payload = buf.ToByteArray(buf.Position);
fixed(byte* p = payload)
fixed (byte* p = payload)
{
reader.Initialise(p, payload.Length);
}
if (!CustomEvents.ReadObjects(reader).SequenceEqual(objs))
var result = CustomEvents.ReadObjects(reader);
if (!result.SequenceEqual(objs))
throw new Exception("CustomEvents fail");
Console.WriteLine("CustomEvents OK");
var sArr1 = new TestStruct[200];
var sArr2 = new TestStruct[200];
var sArr3 = new TestStruct[200];
for (int i = 0; i < 200; i++)
{
sArr1[i] = sArr2[i] = new() { val1 = 123, val2 = 456 };
sArr3[i] = new() { val1 = 456, val2 = 789 };
}
fixed (TestStruct* p1 = sArr1, p2 = sArr2, p3 = sArr3)
{
Debug.Assert(CoreUtils.MemCmp(p1, p2, sizeof(TestStruct)));
Debug.Assert(!CoreUtils.MemCmp(p1, p3, sizeof(TestStruct)));
Debug.Assert(!CoreUtils.MemCmp(p2, p3, sizeof(TestStruct)));
}
#if !DEBUG
var summary = BenchmarkRunner.Run<MemCmpTest>();
#endif
}
public class MemCmpTest
{
private const int N = 10000;
TestStruct* p1;
TestStruct* p2;
TestStruct* p3;
int size = sizeof(TestStruct) * N;
public MemCmpTest()
{
p1 = (TestStruct*)Marshal.AllocHGlobal(N * sizeof(TestStruct));
p2 = (TestStruct*)Marshal.AllocHGlobal(N * sizeof(TestStruct));
p3 = (TestStruct*)Marshal.AllocHGlobal(N * sizeof(TestStruct));
for (int i = 0; i < 200; i++)
{
p1[i] = p2[i] = new() { val1 = 123, val2 = 456 };
p3[i] = new() { val1 = 456, val2 = 789 };
}
}
[Benchmark]
public void Simd()
{
CoreUtils.MemCmp(p1, p2, size);
}
[Benchmark]
public void Win32()
{
memcmp(p1, p2, (UIntPtr)size);
}
}
[DllImport("msvcrt.dll")]
public static extern int memcmp(void* b1, void* b2, UIntPtr count);
}
}

View File

@ -5,8 +5,13 @@
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<OutDir>$(SolutionDir)bin\Tools\UnitTest</OutDir>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
</ItemGroup>

@ -1 +1 @@
Subproject commit 3b64c652e97233a9b384b034475a45d90b1ca0ff
Subproject commit ade2fba5b50b49467c147c1ef4bace0417ed237b