Skip to content

Commit

Permalink
Most of the way to multithreading
Browse files Browse the repository at this point in the history
  • Loading branch information
ari-steas committed Mar 15, 2024
1 parent edfb294 commit a6603da
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -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;
/// <summary>
/// Thread-safe buffer array for active projectiles; cannot be written to while the thread is active.
/// </summary>
Projectile[] ActiveProjectiles = new Projectile[0];
/// <summary>
/// Thread safe buffer list for projectiles to close.
/// </summary>
List<Projectile> ProjectilesToClose = new List<Projectile>();

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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -270,44 +275,56 @@ public float CheckHits()
projectile.Health -= damageToProjectilesInAoE;
}

if (RemainingImpacts > 0)
{
List<IHitInfo> intersects = new List<IHitInfo>();
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,35 @@ public partial class ProjectileManager : MySessionComponentBase
public static ProjectileManager I = new ProjectileManager();
public ProjectileNetwork Network = new ProjectileNetwork();

private Dictionary<uint, Projectile> ActiveProjectiles = new Dictionary<uint, Projectile>();
internal Dictionary<uint, Projectile> ActiveProjectiles = new Dictionary<uint, Projectile>();
private HashSet<Projectile> ProjectilesWithHealth = new HashSet<Projectile>();
public uint NextId { get; private set; } = 0;
private List<Projectile> QueuedCloseProjectiles = new List<Projectile>();
internal List<Projectile> QueuedCloseProjectiles = new List<Projectile>();
/// <summary>
/// Delta for engine ticks; 60tps
/// </summary>
private const float deltaTick = 1 / 60f;
public const float DeltaTick = 1 / 60f;
/// <summary>
/// Delta for frames; varies
/// </summary>
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()
Expand All @@ -53,7 +57,7 @@ public override void UpdateAfterSimulation()

try
{
MyAPIGateway.Parallel.ForEach(ActiveProjectiles.Values.ToArray(), UpdateSingleProjectile);
ProjectileThread.Update();

foreach (var projectile in QueuedCloseProjectiles)
{
Expand All @@ -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();
Expand Down

0 comments on commit a6603da

Please sign in to comment.