I think I made a huge error when I first started designing my game and would give you some or even all of my code but it is just too complex. So please bear with me.
Now that I get to the more advanced stages of my design I am running into major trouble with threading.
During update I run 3 tasks simultaneously. Update, HitTesting and AI which are even split up into even more threads.
- Updating needs to do the movement (including physics) and animation of all my objects.
- HitTesting does all Hit testing between thousands of objects and still needs a lot of optimizing…. Things like Devide and conquer to get right.
- AI issues commands to objects that are executed by the update cycle. Things like turn left or right, fire, etc.
And of course we have
- draw… or in my case GetSprites during draw. That must have full priority over all but the update process.
- And the yet unimplemented sound and message system.
As you can see this is not an optimal process for multitasking as they all function on the same objects but it needs to be.
So… I thought to implement System.Threading.ReaderWriterLockSlim
And here is my real question: How do I do that?
- Since Update is the only writer to my draw data.
- Drawing is a pure Reader
- HitTesting might need to recalculate the boundingRectangle and Matrix but that does not influence Drawing
- AI only needs to read position data and issue commands, read by Update on the next cycle and is separated in an group of separate classes (I call it master/puppet but probably has an official/better name)
Does it make sense to implement different ReaderWriterLockSlim objects to lock the different properties a thread could need?
I would like to have control during update to bypass a locked object (either by AI or HitTesting) and take the next one so that I can do it later when it is unlocked (or even skip it if update is taking too long but do it on the next cycle)
Does any of you know a book or site on advanced threading. Not the usual little examples I find everywhere so I can figure this out?
I have been stuck for more than a week now and I would like to proceed.
Any help appreciated.
This is the code I use for collision detection between objects. I converted it from a c++ example I found a long time ago but can't remember where.
public abstract class HitTestInfo : Object
{
static protected Random RND = new Random();
static protected Dictionary<String, Color[]> m_TextureDataDictionary;
public static Matrix GetMatrix(iSpriteInfo vDrawObject)
{
Matrix Transform =
Matrix.CreateTranslation(new Vector3(-vDrawObject.Origin, 0.0f)) *
Matrix.CreateScale(vDrawObject.Scale) *
Matrix.CreateRotationZ(vDrawObject.Angle) *
Matrix.CreateTranslation(new Vector3(vDrawObject.X, vDrawObject.Y, 0.0f));
return Transform;
}
/// <summary>
/// Calculates an axis aligned rectangle which fully contains an arbitrarily
/// transformed axis aligned rectangle.
/// </summary>
/// <param name="rectangle">Original bounding rectangle.</param>
/// <param name="transform">World transform of the rectangle.</param>
/// <returns>A new rectangle which contains the trasnformed rectangle.</returns>
public static Rectangle CalculateBoundingRectangle(Rectangle vrectangle,
Matrix transform)
{
Rectangle rectangle = vrectangle;
rectangle.X = 0;
rectangle.Y = 0;
// Get all four corners in local space
Vector2 leftTop = new Vector2(rectangle.Left, rectangle.Top);
Vector2 rightTop = new Vector2(rectangle.Right, rectangle.Top);
Vector2 leftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
Vector2 rightBottom = new Vector2(rectangle.Right, rectangle.Bottom);
// Transform all four corners into work space
Vector2.Transform(ref leftTop, ref transform, out leftTop);
Vector2.Transform(ref rightTop, ref transform, out rightTop);
Vector2.Transform(ref leftBottom, ref transform, out leftBottom);
Vector2.Transform(ref rightBottom, ref transform, out rightBottom);
// Find the minimum and maximum extents of the rectangle in world space
Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop),
Vector2.Min(leftBottom, rightBottom));
Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop),
Vector2.Max(leftBottom, rightBottom));
// Return that as a rectangle
return new Rectangle((int)min.X, (int)min.Y,
(int)(max.X - min.X), (int)(max.Y - min.Y));
}
/// <summary>
/// Determines if there is overlap of the non-transparent pixels between two
/// sprites.
/// </summary>
/// <param name="transformA">World transform of the first sprite.</param>
/// <param name="widthA">Width of the first sprite's texture.</param>
/// <param name="heightA">Height of the first sprite's texture.</param>
/// <param name="dataA">Pixel color data of the first sprite.</param>
/// <param name="transformB">World transform of the second sprite.</param>
/// <param name="widthB">Width of the second sprite's texture.</param>
/// <param name="heightB">Height of the second sprite's texture.</param>
/// <param name="dataB">Pixel color data of the second sprite.</param>
/// <returns>True if non-transparent pixels overlap; false otherwise</returns>
public static bool IntersectPixels(
Matrix transformA, int widthA, int heightA, Color[] dataA,
Matrix transformB, int widthB, int heightB, Color[] dataB)
{
// Calculate a matrix which transforms from A's local space into
// world space and then into B's local space
Matrix transformAToB = transformA * Matrix.Invert(transformB);
// When a point moves in A's local space, it moves in B's local space with a
// fixed direction and distance proportional to the movement in A.
// This algorithm steps through A one pixel at a time along A's X and Y axes
// Calculate the analogous steps in B:
Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);
// Calculate the top left corner of A in B's local space
// This variable will be reused to keep track of the start of each row
Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);
// For each row of pixels in A
for (int yA = 0; yA < heightA; yA++)
{
// Start at the beginning of the row
Vector2 posInB = yPosInB;
// For each pixel in this row
for (int xA = 0; xA < widthA; xA++)
{
// Round to the nearest pixel
int xB = (int)Math.Round(posInB.X);
int yB = (int)Math.Round(posInB.Y);
// If the pixel lies within the bounds of B
if (0 <= xB && xB < widthB &&
0 <= yB && yB < heightB)
{
// Get the colors of the overlapping pixels
Color colorA = dataA[xA + yA * widthA];
Color colorB = dataB[xB + yB * widthB];
// If both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
{
// then an intersection has been found
return true;
}
}
// Move to the next pixel in the row
posInB += stepX;
}
// Move to the next row
yPosInB += stepY;
}
// No intersection found
return false;
}
public static List<CollisionData> CollisionCheck<T1, T2>(List<T1> List1, List<T2> List2)
{
List<CollisionData> RetList = new List<CollisionData>();
foreach (T1 obj1 in List1)
{
iSpriteInfo SI1 = obj1 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix1 = SI1.Matrix;
Rectangle Rect1 = SI1.BoundingRectangle;
Color[] TextureData1 = SI1.TextureData;
foreach (T2 obj2 in List2)
{
iSpriteInfo SI2 = obj2 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix2 = SI2.Matrix;
Rectangle Rect2 = SI2.BoundingRectangle;
Color[] TextureData2 = SI2.TextureData;
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if (Rect1.Intersects(Rect2))
{
// Check collision with Player and planets
if (IntersectPixels(Matrix1, (int)SI1.DestinationRectangle.Width,
(int)SI1.DestinationRectangle.Height, TextureData1,
Matrix2, (int)SI2.DestinationRectangle.Width,
(int)SI2.DestinationRectangle.Height, TextureData2))
{
RetList.Add(new CollisionData(SI1, SI2));
}
}
}
}
}
}
return RetList;
}
public static List<CollisionData> CollisionCheck<T1, T2>(T1 Obj1, List<T2> List2)
{
List<CollisionData> RetList = new List<CollisionData>();
lock (Obj1)
{
lock (List2)
{
iSpriteInfo SI1 = Obj1 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix1 = SI1.Matrix;
Rectangle Rect1 = SI1.BoundingRectangle;
Color[] TextureData1 = SI1.TextureData;
foreach (T2 obj2 in List2)
{
iSpriteInfo SI2 = obj2 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix2 = SI2.Matrix;
Rectangle Rect2 = SI2.BoundingRectangle;
Color[] TextureData2 = SI2.TextureData;
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if (Rect1.Intersects(Rect2))
{
// Check collision with Player and planets
if (IntersectPixels(Matrix1, (int)SI1.DestinationRectangle.Width,
(int)SI1.DestinationRectangle.Height, TextureData1,
Matrix2, (int)SI2.DestinationRectangle.Width,
(int)SI2.DestinationRectangle.Height, TextureData2))
{
RetList.Add(new CollisionData(SI1, SI2));
}
}
}
}
}
}
}
return RetList;
}
public static bool CollisionCheck<T1, T2>(T1 Obj1, T2 Obj2)
{
Matrix Matrix1;
Rectangle Rect1;
Color[] TextureData1;
Matrix Matrix2;
Rectangle Rect2;
Color[] TextureData2;
iSpriteInfo SI1 = Obj1 as iSpriteInfo;
if (SI1 != null)
{
lock (SI1)
{
Matrix1 = SI1.Matrix;
Rect1 = SI1.BoundingRectangle;
TextureData1 = SI1.TextureData;
}
iSpriteInfo SI2 = Obj2 as iSpriteInfo;
if (SI1 != null)
{
lock (SI2)
{
Matrix2 = SI2.Matrix;
Rect2 = SI2.BoundingRectangle;
TextureData2 = SI2.TextureData;
}
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if (Rect1.Intersects(Rect2))
{
// Check collision with Player and planets
if (IntersectPixels(Matrix1, (int)SI1.DestinationRectangle.Width,
(int)SI1.DestinationRectangle.Height, TextureData1,
Matrix2, (int)SI2.DestinationRectangle.Width,
(int)SI2.DestinationRectangle.Height, TextureData2))
{
return true;
}
}
}
}
return false;
}
}
iSpriteInfo is defined like this
public interface iSpriteInfo
{
float X { get; set; }
float Y { get; set; }
float Angle { get; set; }
Vector2 Origin { get; set; }
float Scale { get; set; }
float Depth { get; set; }
Color Color { get; set; }
Boolean Visible { get; set; }
Rectangle SourceRectangle { get; set; }
Rectangle DestinationRectangle { get; set; }
Rectangle BoundingRectangle { get; }
Matrix Matrix { get; }
SpriteSheet SpriteSheet { get; set; }
int SpriteSheetNum { get;}
Color[] TextureData { get; set; }
Vector2 GetVector2 { get; }
Vector3 GetVector3 { get; }
}