Search code examples
c#.netclassstructa-star

Using a struct is OK, using a class throws OutOfMemoryException (A* algorithm)


I'm going through a quite troublesome problem and I don't know what happens and why it happens. I'm working on a quite high-intensive pathfinding algorithm (A*) and I'm doing quite a good number of assignments and manipulation over this struct:

public struct CellInfo
{
    public int MinCost;
    public CPos Path;
    public bool Seen;

    public CellInfo(int minCost, CPos path, bool seen)
    {
        MinCost = minCost;
        Path = path;
        Seen = seen;
    }
}

This struct is mutable because I need to update the values that are within, so I thought it would be a good idea to turn it into a class. However, making the simple change of "struct" to "class" ends up leading, short after application startup, to an OutOfMemoryException and I don't know why. Can someone shed some light about this issue?

This is the most intensive portion of the algorithm just for reference:

public CPos Expand(IWorld world)
    {
        var currentMinNode = OpenQueue.Pop();
        while (CellInfo[currentMinNode.Location].Seen)
        {
            if (OpenQueue.Empty)
                return currentMinNode.Location;

            currentMinNode = OpenQueue.Pop();
        }

        var pCell = CellInfo[currentMinNode.Location];
        pCell.Seen = true;
        CellInfo[currentMinNode.Location] = pCell;

        // This current cell is ok; check all immediate directions:
        Considered.Add(currentMinNode.Location);

        var directions = GetNeighbors(currentMinNode.Location, pCell.Path);

        for (var i = 0; i < directions.Length; ++i)
        {
            var direction = directions[i];

            var neighborCPos = currentMinNode.Location + direction;

            // Is this direction flat-out unusable or already seen?
            // TODO: The "as Actor" is made to just isolate this clase, but in the future
            // everything should use IActor implementation instead of concrete class.
            if (!world.IMap.Contains(neighborCPos) ||
                CellInfo[neighborCPos].Seen ||
                !mobileInfo.CanEnterCell(world as World, Self as Actor, neighborCPos, IgnoredActor as Actor, CheckForBlocked ? CellConditions.TransientActors : CellConditions.None) ||
                (customBlock != null && customBlock(neighborCPos)))
                continue;

            var cellCost = CalculateCellCost(world, neighborCPos, direction);
            var gCost = CellInfo[currentMinNode.Location].MinCost + cellCost;

            // Cost is even higher; next direction:
            if (gCost > CellInfo[neighborCPos].MinCost)
                continue;

            // Now we may seriously consider this direction using heuristics:
            var hCost = Heuristic(neighborCPos);

            var neighborCell = CellInfo[neighborCPos];
            neighborCell.Path = currentMinNode.Location;
            neighborCell.MinCost = gCost;
            CellInfo[neighborCPos] = neighborCell;

            OpenQueue.Add(new PathDistance(gCost + hCost, neighborCPos));

            if (gCost > MaxCost)
                MaxCost = gCost;

            Considered.Add(neighborCPos);
        }

        // Sort to prefer the cheaper direction:
        // Array.Sort(nextDirections, (a, b) => a.Second.CompareTo(b.Second));
        return currentMinNode.Location;
    }

Solution

  • I found the solution to my problem. When it was a struct, everytime I was passing around variables they were copies instead of references, so every new data structure composed of CellInfo I created was brand new.

    When changing to class, instead of copies they were references, and at some points of my code this caused several unwanted side effects that ended up in a infinite loop, which ultimately provoked the out of memory error.

    Yeah, after all it was a problem of tracking down the problem, memory profiling, doing experiments and find the reason why a certain variable has a weird value.

    Thanks for all your answers!