From a6603dacfd1db5c00c299dd4ac5f12f9ee1ac095 Mon Sep 17 00:00:00 2001 From: Aristeas <94058548+Jnick-24@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:14:22 -0500 Subject: [PATCH] Most of the way to multithreading --- .../Projectiles/ParallelProjectileThread.cs | 77 +++++++++++++++++++ .../HeartModule/Projectiles/Projectile.cs | 73 +++++++++++------- .../Projectiles/ProjectileManager.cs | 20 ++--- 3 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ParallelProjectileThread.cs diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ParallelProjectileThread.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ParallelProjectileThread.cs new file mode 100644 index 0000000..0be1d52 --- /dev/null +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ParallelProjectileThread.cs @@ -0,0 +1,77 @@ +using Heart_Module.Data.Scripts.HeartModule.ExceptionHandler; +using ParallelTasks; +using Sandbox.ModAPI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Heart_Module.Data.Scripts.HeartModule.Projectiles +{ + internal class ParallelProjectileThread + { + #region variables + + Task thisTask; + /// + /// Thread-safe buffer array for active projectiles; cannot be written to while the thread is active. + /// + Projectile[] ActiveProjectiles = new Projectile[0]; + /// + /// Thread safe buffer list for projectiles to close. + /// + List ProjectilesToClose = new List(); + + public float DeltaTick = 0; + + #endregion + + public ParallelProjectileThread() + { + thisTask = MyAPIGateway.Parallel.StartBackground(DoWork); + HeartLog.Log("Started ParallelProjectileThread!"); + } + + #region methods + + public void Update() + { + DeltaTick += ProjectileManager.DeltaTick; + + if (thisTask.IsComplete) + { + // Update thread-safe buffer lists + ActiveProjectiles = ProjectileManager.I.ActiveProjectiles.Values.ToArray(); + ProjectileManager.I.QueuedCloseProjectiles.AddRange(ProjectilesToClose); + ProjectilesToClose.Clear(); + thisTask = MyAPIGateway.Parallel.StartBackground(DoWork); + } + } + + public void Close() + { + HeartLog.Log("-------------------------------------------"); + HeartLog.Log(" Closing ParallelProjectileThread..."); + if (!thisTask.IsComplete) + thisTask.Wait(true); + HeartLog.Log(" Closed ParallelProjectileThread."); + HeartLog.Log("-------------------------------------------"); + } + + void DoWork() + { + MyAPIGateway.Parallel.ForEach(ActiveProjectiles, UpdateSingleProjectile); + DeltaTick = 0; + } + + void UpdateSingleProjectile(Projectile projectile) + { + projectile.TickUpdate(DeltaTick); + + if (projectile.QueuedDispose) + ProjectilesToClose.Add(projectile); + } + + #endregion + } +} diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs index 8f66917..b2e29d4 100644 --- a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs @@ -4,9 +4,11 @@ using Sandbox.ModAPI; using System; using System.Collections.Generic; +using System.Drawing; using VRage.Game.Entity; using VRage.Game.ModAPI; using VRageMath; +using static Sandbox.Engine.Physics.MyPhysics.CollisionLayers; namespace Heart_Module.Data.Scripts.HeartModule.Projectiles { @@ -149,6 +151,9 @@ public void TickUpdate(float delta) if ((Definition.PhysicalProjectile.MaxTrajectory != -1 && Definition.PhysicalProjectile.MaxTrajectory < DistanceTravelled) || (Definition.PhysicalProjectile.MaxLifetime != -1 && Definition.PhysicalProjectile.MaxLifetime < Age)) QueueDispose(); + if (QueuedDispose) + return; + if (Guidance == null && Definition.Guidance.Length > 0) Guidance = new ProjectileGuidance(this); @@ -270,44 +275,56 @@ public float CheckHits() projectile.Health -= damageToProjectilesInAoE; } - if (RemainingImpacts > 0) - { - List intersects = new List(); - MyAPIGateway.Physics.CastRay(Position, NextMoveStep, intersects); + dist = PerformRaycastRecursive(len); - foreach (var hitInfo in intersects) - { - if (RemainingImpacts <= 0) - break; + if (RemainingImpacts <= 0) + QueueDispose(); - if (hitInfo.HitEntity.EntityId == Firer) - continue; // Skip firer + return (float)dist; + } - dist = hitInfo.Fraction * len; + private double PerformRaycastRecursive(double length) + { + if (RemainingImpacts <= 0) + return -1; - if (MyAPIGateway.Session.IsServer) - { - if (hitInfo.HitEntity is IMyCubeGrid) - DamageHandler.QueueEvent(new DamageEvent(hitInfo.HitEntity, DamageEvent.DamageEntType.Grid, this, hitInfo.Position, hitInfo.Normal, Position, NextMoveStep)); - else if (hitInfo.HitEntity is IMyCharacter) - DamageHandler.QueueEvent(new DamageEvent(hitInfo.HitEntity, DamageEvent.DamageEntType.Character, this, hitInfo.Position, hitInfo.Normal, Position, NextMoveStep)); - } + double dist = -1; - if (MyAPIGateway.Session.IsServer) - PlayImpactAudio(hitInfo.Position); // Audio is global - if (!MyAPIGateway.Utilities.IsDedicated) - DrawImpactParticle(hitInfo.Position, hitInfo.Normal); // Visuals are clientside + MyAPIGateway.Physics.CastRayParallel(ref Position, ref NextMoveStep, NoVoxelCollisionLayer, (hitInfo) => + { + if (RemainingImpacts <= 0 || hitInfo.HitEntity.EntityId == Firer) + return; - Definition.LiveMethods.OnImpact?.Invoke(Id, hitInfo.Position, Direction, (MyEntity)hitInfo.HitEntity); + dist = hitInfo.Fraction * length; - RemainingImpacts--; + if (MyAPIGateway.Session.IsServer) + { + if (hitInfo.HitEntity is IMyCubeGrid) + DamageHandler.QueueEvent(new DamageEvent(hitInfo.HitEntity, DamageEvent.DamageEntType.Grid, this, hitInfo.Position, hitInfo.Normal, Position, NextMoveStep)); + else if (hitInfo.HitEntity is IMyCharacter) + DamageHandler.QueueEvent(new DamageEvent(hitInfo.HitEntity, DamageEvent.DamageEntType.Character, this, hitInfo.Position, hitInfo.Normal, Position, NextMoveStep)); } - } - if (RemainingImpacts <= 0) - QueueDispose(); + if (MyAPIGateway.Session.IsServer) + PlayImpactAudio(hitInfo.Position); // Audio is global + if (!MyAPIGateway.Utilities.IsDedicated) + DrawImpactParticle(hitInfo.Position, hitInfo.Normal); // Visuals are clientside - return (float)dist; + Definition.LiveMethods.OnImpact?.Invoke(Id, hitInfo.Position, Direction, (MyEntity)hitInfo.HitEntity); + + RemainingImpacts--; + }); + + if (dist == -1) + return dist; + + double nextDist = PerformRaycastRecursive(length); + + if (nextDist == -1) + return dist; + + // Get the furthest impact distance. + return Math.Max(dist, nextDist); } public Vector3D NextMoveStep = Vector3D.Zero; diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs index b539c54..e973a25 100644 --- a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs @@ -20,31 +20,35 @@ public partial class ProjectileManager : MySessionComponentBase public static ProjectileManager I = new ProjectileManager(); public ProjectileNetwork Network = new ProjectileNetwork(); - private Dictionary ActiveProjectiles = new Dictionary(); + internal Dictionary ActiveProjectiles = new Dictionary(); private HashSet ProjectilesWithHealth = new HashSet(); public uint NextId { get; private set; } = 0; - private List QueuedCloseProjectiles = new List(); + internal List QueuedCloseProjectiles = new List(); /// /// Delta for engine ticks; 60tps /// - private const float deltaTick = 1 / 60f; + public const float DeltaTick = 1 / 60f; /// /// Delta for frames; varies /// private Stopwatch clockTick = Stopwatch.StartNew(); + ParallelProjectileThread ProjectileThread; + public int NumProjectiles => ActiveProjectiles.Count; public override void LoadData() { I = this; DamageHandler.Load(); + ProjectileThread = new ParallelProjectileThread(); } protected override void UnloadData() { I = null; DamageHandler.Unload(); + ProjectileThread.Close(); } public override void UpdateAfterSimulation() @@ -53,7 +57,7 @@ public override void UpdateAfterSimulation() try { - MyAPIGateway.Parallel.ForEach(ActiveProjectiles.Values.ToArray(), UpdateSingleProjectile); + ProjectileThread.Update(); foreach (var projectile in QueuedCloseProjectiles) { @@ -76,14 +80,6 @@ public override void UpdateAfterSimulation() } } - private void UpdateSingleProjectile(Projectile projectile) - { - projectile.TickUpdate(deltaTick); - - if (projectile.QueuedDispose) - QueuedCloseProjectiles.Add(projectile); - } - public override void UpdatingStopped() { clockTick.Stop();