2

背景:我有一款游戏,我主要在(低规格)笔记本电脑上进行测试。它运行良好。在 xbox 上进行测试时,有一种方法在被调用时似乎会严重影响性能/fps。在 PC 上,您不会注意到任何减速/跳帧。

我在 xbox 上进行了分析,平均而言,我每秒获得 1-2 次 GC,游戏运行时需要 20-40 毫秒。

当我的慢速方法运行时,我注意到 GC 率或持续时间没有变化。

接下来,我尝试在 PC 上进行分析,以确定该方法中花费的时间最多的部分。原来它在做List<T>.Contains(),所以我创建了我自己的类,它在内部有 aList<T>和 a HashSet<T>,所以我可以在HashSet<T>内部使用 for Contains()

我现在已经到了这样的地步,在不改变算法的情况下我真的想不出还有什么要调整的,我认为算法就像它会得到的一样简单。

我知道我无法在 xbox 上获取方法时间/百分比,所以我对接下来要尝试什么感到有点茫然。

我已经包含了下面的代码,用于在基于瓷砖的系统中模拟流动的水。它每个水格运行一次,试图向下移动它可能通过其他水格(一般来说)。

问题:我想知道我是否在这里做任何明显错误的事情(即会严重影响 Xbox 性能)。打电话Func很慢吗?我是否可以在任何地方使用我的Point物品进行拳击?等等

适用于大量代码!瓦片本身来自对象池以最小化 GC。该InternalThink()方法是导致所有问题的原因。

public abstract class TileFlowing : TileMovable
{
    private FastList<Point> TeleportSwapLocations = new FastList<Point>();

    private FastList<Point> PossibleMoveLocations = new FastList<Point>();

    private FastQueue<Point> PositionsToCheck = new FastQueue<Point>();

    private FastList<Point> PositionsChecked = new FastList<Point>();

    private static Comparison<Point> _PossibleMoveComparer;

    public bool Static = false;

    protected abstract Func<Point, int> PossibleMoveLocationOrdering { get; }

    protected abstract Func<Point, Point, bool, bool> MoveSidewaysFunc { get; }

    protected abstract int MaxUnitsWithin { get; }

    protected abstract int Weight { get; }

    public int UnitsWithin;

    public int AvailableToFlowThrough = 0;

    protected virtual bool RecurseTilesUp { get { return true; } }
    protected virtual bool RecurseTilesDown { get { return true; } }
    protected virtual bool RecurseTilesLeft { get { return true; } }
    protected virtual bool RecurseTilesRight { get { return true; } }

    public TileFlowing()
        : base()
    {

    }

    public override void LoadContent(Components.TileGridManagement.GameGrid Owner)
    {
        base.LoadContent(Owner);

        _PossibleMoveComparer = (Point P1, Point P2) =>
        {
            int Result = PossibleMoveLocationOrdering(P1) -
                            PossibleMoveLocationOrdering(P2);
            if (Result == 0)
                Result = (IsSameType(P1) ? (_Owner[P1] as TileFlowing).UnitsWithin : 0) -
                            (IsSameType(P2) ? (_Owner[P2] as TileFlowing).UnitsWithin : 0);

            return Result;
        };
    }

    public override void ResetProperties()
    {
        base.ResetProperties();

        Static = false;
        UnitsWithin = MaxUnitsWithin;
        AvailableToFlowThrough = 0;
    }

    public override void CopyProperties(Tile SourceTile)
    {
        base.CopyProperties(SourceTile);

        Static = (SourceTile as TileFlowing).Static;
        UnitsWithin = (SourceTile as TileFlowing).UnitsWithin;
        AvailableToFlowThrough = (SourceTile as TileFlowing).AvailableToFlowThrough;
    }

    public override void Think()
    {
        base.Think();

        InternalThink(false, false);
    }

    public override void FactoryThink()
    {
        base.FactoryThink();

        InternalThink(true, false);
    }

    public void FlowThink(bool CalledFromFactoryThink)
    {
        InternalThink(CalledFromFactoryThink, true);
    }

    private bool IsSameType(Point Position)
    {
        return IsSameType(Position.X, Position.Y);
    }

    private bool IsSameType(int X, int Y)
    {
        return _Owner[X, Y] != null && _Owner[X, Y].GetType() == GetType();
    }

    private bool IsDifferentFlowingTile(Point Position)
    {
        return IsDifferentFlowingTile(Position.X, Position.Y);
    }

    private bool IsDifferentFlowingTile(int X, int Y)
    {
        return !IsSameType(X, Y) && _Owner[X, Y] is TileFlowing;
    }

    protected void CheckPosition(Point PositionToCheck, Point TilePosition, bool CalledFromFactoryThink, bool CalledFromFlowThink,
                                 ref FastList<Point> PossibleMoveLocations, ref FastList<Point> TeleportSwapLocations, ref FastQueue<Point> PositionsToCheck,
                                 Func<Point, Point, bool, bool> ClearCheckFunc)
    {
        if (IsSameType(PositionToCheck))
        {
            if (!PositionsToCheck.Contains(PositionToCheck))
                PositionsToCheck.Enqueue(PositionToCheck);
        }
        else if (_Owner[PositionToCheck] is TileFlowing && (ClearCheckFunc == null || ClearCheckFunc(PositionToCheck, TilePosition, CalledFromFactoryThink)))
        {
            // If we weigh more than the other tile, or we're called from the factory think (are under pressure)
            if ((_Owner[PositionToCheck] as TileFlowing).Weight < Weight || CalledFromFactoryThink)
            {
                if (!(_Owner[PositionToCheck] as TileFlowing).Static || !CalledFromFlowThink)
                    PossibleMoveLocations.Add(PositionToCheck);
            }
        }
        else if (_Owner.IsClear(PositionToCheck) && (ClearCheckFunc == null || ClearCheckFunc(PositionToCheck, TilePosition, CalledFromFactoryThink)))
        {
            PossibleMoveLocations.Add(PositionToCheck);
        }
    }

    private int PossibleMoveLocationsComparer(Point P1, Point P2)
    {
        return (PossibleMoveLocationOrdering(P1) - PossibleMoveLocationOrdering(P2)) * 1000 +
               ((IsSameType(P1) ? (_Owner[P1] as TileFlowing).UnitsWithin : 0) - (IsSameType(P2) ? (_Owner[P2] as TileFlowing).UnitsWithin : 0)) * 100;
    }

    protected void InternalThink(bool CalledFromFactoryThink, bool CalledFromFlowThink)
    {
        AvailableToFlowThrough = 0;

        TeleportSwapLocations.Clear();

        PossibleMoveLocations.Clear();

        PositionsToCheck.Clear();

        PositionsChecked.Clear();

        PositionsToCheck.Enqueue(Position);

        while (PositionsToCheck.Count != 0)
        {
            Point PositionToCheck = PositionsToCheck.Dequeue();

            if (!PositionsChecked.Contains(PositionToCheck) &&
                ((_Owner[PositionToCheck] as TileFlowing).AvailableToFlowThrough < MaxUnitsWithin || CalledFromFactoryThink))
            {
                if (((_Owner[PositionToCheck] as TileFlowing).Static && !CalledFromFactoryThink))
                    continue;

                if (PositionToCheck != Position)
                {
                    (_Owner[PositionToCheck] as TileFlowing).AvailableToFlowThrough++;
                }

                PositionsChecked.Add(PositionToCheck);

                if ((_Owner[PositionToCheck] as TileFlowing).UnitsWithin < MaxUnitsWithin && PositionToCheck != Position)
                {
                    PossibleMoveLocations.Add(PositionToCheck);

                    if (CalledFromFactoryThink && (_Owner[PositionToCheck] as TileFlowing).UnitsWithin + UnitsWithin <= MaxUnitsWithin)
                        continue;
                }

                // Check below
                Point PosBelow = new Point(PositionToCheck.X + TileDirection.Down.X, PositionToCheck.Y + TileDirection.Down.Y);

                CheckPosition(PosBelow, Position, CalledFromFactoryThink, CalledFromFlowThink, ref PossibleMoveLocations, ref TeleportSwapLocations, ref PositionsToCheck, null);

                // Check one horizontal direction
                Point RandHDir = Randomiser.GetHDirection();

                Point RandHPos = new Point(RandHDir.X + PositionToCheck.X, RandHDir.Y + PositionToCheck.Y);

                CheckPosition(RandHPos, Position, CalledFromFactoryThink, CalledFromFlowThink, ref PossibleMoveLocations, ref TeleportSwapLocations, ref PositionsToCheck, MoveSidewaysFunc);

                // Check the other horizontal direction
                Point OtherHDir = new Point(-RandHDir.X, RandHDir.Y);

                Point OtherHPos = new Point(OtherHDir.X + PositionToCheck.X, OtherHDir.Y + PositionToCheck.Y);

                CheckPosition(OtherHPos, Position, CalledFromFactoryThink, CalledFromFlowThink, ref PossibleMoveLocations, ref TeleportSwapLocations, ref PositionsToCheck, MoveSidewaysFunc);

                // Check above if appropriate
                Point AbovePos = new Point(PositionToCheck.X + TileDirection.Up.X, PositionToCheck.Y + TileDirection.Up.Y);

                if (TileDirection.Below(AbovePos, Position) || CalledFromFactoryThink)
                {
                    CheckPosition(AbovePos, Position, CalledFromFactoryThink, CalledFromFlowThink, ref PossibleMoveLocations, ref TeleportSwapLocations, ref PositionsToCheck, null);
                }
            }
        }

        PossibleMoveLocations.Sort(_PossibleMoveComparer);

        bool Moved = false;

        if (PossibleMoveLocations.Count != 0)
        {
            if (CalledFromFactoryThink)
            {
                while (UnitsWithin != 0 && PossibleMoveLocations.Count != 0)
                {
                    int OldUnitsWithin = UnitsWithin;

                    Moved = IterateTeleport(CalledFromFactoryThink, ref PossibleMoveLocations, (P) => !IsDifferentFlowingTile(P), Moved);

                    if (UnitsWithin == OldUnitsWithin)
                    {
                        Moved = IterateTeleport(CalledFromFactoryThink, ref PossibleMoveLocations, (P) => IsDifferentFlowingTile(P), Moved);
                    }

                    PossibleMoveLocations.RemoveAll(P => IsSameType(P) && (_Owner[P] as TileFlowing).UnitsWithin == MaxUnitsWithin);
                }
            }
            else
            {
                Moved = Moved || Teleport(PossibleMoveLocations[0]);
            }

            // If we did move and not because we were forced to then mark all mercury tiles above and left or right as not static.
            if (!CalledFromFactoryThink)
            {
                _Owner.RecurseTiles(Position,
                                    (P) => RecurseTilesUp && IsSameType(P.X + TileDirection.Up.X, P.Y + TileDirection.Up.Y),
                                    (P) => RecurseTilesDown && IsSameType(P.X + TileDirection.Down.X, P.Y + TileDirection.Down.Y),
                                    (P) => RecurseTilesLeft && IsSameType(P.X + TileDirection.Left.X, P.Y + TileDirection.Left.Y),
                                    (P) => RecurseTilesRight && IsSameType(P.X + TileDirection.Right.X, P.Y + TileDirection.Right.Y),
                                    (P) =>
                                    {
                                        if (IsSameType(P))
                                            (_Owner[P] as TileFlowing).Static = false;
                                    });
            }
        }
        else
        {
            // Mark this tile as static if we didn't move, no water moved through this tile and we have all the units we can take.
            Static = (AvailableToFlowThrough == 0) && (UnitsWithin == MaxUnitsWithin); // TODO: 9 Fix flowing tiles becoming static and getting stuck
        }

        if (!Moved)
        {
            // If we haven't moved
            if (TeleportSwapLocations.Count != 0)
            {
                Moved = TeleportSwap(TeleportSwapLocations[0]);
            }

            if(!Moved)
            {
                // If we didn't move, undo checked tiles
                foreach (var CheckedPosition in PositionsChecked)
                {
                    (_Owner[CheckedPosition] as TileFlowing).AvailableToFlowThrough--;
                }
            }
        }
    }

    private bool IterateTeleport(bool CalledFromFactoryThink, ref FastList<Point> SortedPossibleMoveLocations, Func<Point, bool> PossibleMoveLocationsFilter, bool Moved)
    {
        foreach (var PossibleMoveLocation in SortedPossibleMoveLocations)
        {
            if (PossibleMoveLocationsFilter(PossibleMoveLocation))
            {
                if (IsDifferentFlowingTile(PossibleMoveLocation))
                {
                    bool OldStatic = Static;

                    Static = true;
                    (_Owner[PossibleMoveLocation] as TileFlowing).FlowThink(CalledFromFactoryThink);
                    Static = OldStatic;
                }

                bool TeleportResult = Teleport(PossibleMoveLocation);
                Moved = Moved || TeleportResult;

                if (TeleportResult)
                    break;
            }
        }
        return Moved;
    }

    protected bool TeleportSwap(Point NewPosition)
    {
        TileFlowing OurNewTile = (TileFlowing)Tile.GetNewTileFromStore(this);
        OurNewTile.CopyProperties(this);
        OurNewTile.Position = NewPosition;

        TileFlowing ReplacedTile = (TileFlowing)Tile.GetNewTileFromStore(_Owner[NewPosition]);
        ReplacedTile.CopyProperties(_Owner[NewPosition]);
        ReplacedTile.Position = Position;

        _Owner.ClearTile(NewPosition);
        _Owner.AddTileToGrid(OurNewTile);

        _Owner.ClearTile(Position);
        _Owner.AddTileToGrid(ReplacedTile);

        UnitsWithin = 0;

        return true;
    }

    protected bool Teleport(Point NewPosition)
    {
        if (IsDifferentFlowingTile(NewPosition))
        {
            return TeleportSwap(NewPosition);
        }
        else
        {

            TileFlowing NewTile;

            bool RemovedAllUnits = false;

            int NewPositionUnits = IsSameType(NewPosition) ? (_Owner[NewPosition] as TileFlowing).UnitsWithin : 0;

            int UnitsToRemove = Math.Min(UnitsWithin,
                                         Math.Max(1,
                                                  Math.Min(Math.Abs(UnitsWithin - NewPositionUnits) / 2,
                                                           MaxUnitsWithin - NewPositionUnits)));

            UnitsWithin -= UnitsToRemove;

            if (IsSameType(NewPosition))
            {
                (_Owner[NewPosition] as TileFlowing).UnitsWithin += UnitsToRemove;
            }
            else
            {
                NewTile = (TileFlowing)Tile.GetNewTileFromStore(this);

                NewTile.Position = NewPosition;
                NewTile.UnitsWithin = UnitsToRemove;

                _Owner.AddTileToGrid(NewTile);
            }

            if (UnitsWithin == 0)
            {
                _Owner.ClearTile(Position);
                RemovedAllUnits = true;
            }

            return RemovedAllUnits;
        }
    }
}
4

2 回答 2

0

这里只是一个疯狂的猜测,但是......

这种重复查找并在while循环中强制转换对我来说看起来有点狡猾:_Owner[PositionToCheck] as TileFlowing. 我很想把它作为一个变量拉出来,看看会发生什么。

于 2011-06-14T08:44:13.550 回答
0

我认为你可以使用 aStopwatch来做一些基本的分析。您可以考虑在 think 方法上设置毫秒或迭代“限制”——因此它永远不会花费超过 5 毫秒/200 次迭代或任何可行的方法。

于 2011-06-14T07:41:20.180 回答