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();