Наиболее эффективный способ использования индексаторов многомерных массивов

Скажем, у вас есть 2D-сетка плиток (это для игры, основанной на 2D-плитках), большинство плиток занимают 1 место, однако некоторые более крупные «объекты» могут занимать несколько мест. Я использую индексатор в своем массиве, чтобы автоматически «относить» эти объекты к их базовой плитке. Итак, если у меня есть объект 2x2 на 3,4, и я обращаюсь к 4,4, он автоматически перенаправит и получит тайл на 3,4. Однако я могу указать аргумент, чтобы обойти эту функцию в случае, если мне нужно получить точную плитку. (Лучшее объяснение на мой старый вопрос на GameDev об этом)

Другой способ взглянуть на это — это объект двери в игровом мире, пользователь может щелкнуть в любом месте, чтобы открыть ее, но каждая отдельная часть может содержать другие свойства, такие как разные фоны и значения освещения.

Примечание. Я всего лишь программист-любитель, так что это может быть неправильно (и почему я ищу вашего совета). Каждая «очень большая» плитка будет хранить ссылку на свою базовую плитку в виде позиции X,Y (должно ли это быть вместо этого ссылка на реальный объект в памяти?)

public class TileWrapper
{
    public int Width = 0;
    public int Height = 0;
    private Tile[] tiles; //Backing Store
    public TileWrapper()
    {
        tiles = new Tile[Width * Height]; 
    }
    public TileWrapper(int width, int height)
    {
        Width = width;
        Height = height;
        tiles = new Tile[Width * Height];
    }
    /// <summary>
    /// Accessor for tiles
    /// </summary>
    /// <param name="x">X Position</param>
    /// <param name="y">Y Position</param>
    /// <param name="overide">Bool to "override" the get, if true, it wont get the reference tile and will bypass the checks</param>
    public Tile this[int x, int y, bool override = false]
    {
        get 
        {
             //If we do not want to bypass the checks, AND the current tile is > than 1x1
             if (!override && tiles[y * Width + x].IsLarge)
                return tiles[tiles[y * Width + x].refY * Width + tiles[y * Width + x].refX]; //Use the reference positions to get the main position of the tile
             //If we want to bypass the checks or the tile wasn't large, get the absolute position
             else 
                 return tiles[y * Width + x];
        }
        set 
        {
             //Same thing for SET
             if (!override && tiles[y * Width + x].IsLarge) //Set base tile if the large tile has a reference
                 tiles[tiles[y * Width + x].refY * Width + tiles[y * Width + x].refX] = value;
             else  //Set absolute tile
                  tiles[y * Width + x] = value;
        }
    }

Извините, если это немного сложно читать с преобразованием 2D в 1D, но после некоторого тестирования кажется, что внутреннее использование 1D-массива немного быстрее.

IsLarge — это свойство, которое просто проверяет, является ли плитка большой (больше 1x1).

У меня уже есть логика для заполнения ссылок на соседние плитки при размещении большой плитки и их удаления соответственно.

При профилировании игры я обнаружил, что метод доступа get для плитки потребляет много ресурсов ЦП, получая плитки сотни раз за кадр для освещения, рендеринга, коллизии и т. д.

Как я могу улучшить производительность и эффективность этого кода?

Тестовые тесты (в среднем 30 000 итераций на Intel Quad Core i7 2670QM)

Tile t = tiles[100, 100]; — 160 нс и 175 нс С 2D-внутренним массивом

Tile t = tiles[100, 100, true]; — 137 нс и 264 нс С 2D-внутренним массивом (нечетные)

100,100 между прочим не большая плитка, обратите внимание, что эти плитки не очень распространены. Если бы у вас был дом на экране, вы могли бы иметь несколько больших плиток (двери, столы), но много грязи/камня/дерева.


person Cyral    schedule 12.08.2013    source источник
comment
Я бы посоветовал перегрузить индексатор вместо использования параметра по умолчанию для логического значения. Возможно, используйте именованные методы для одного или другого. Затем вы можете исключить ветвление из доступа к массиву.   -  person lukegravitt    schedule 12.08.2013
comment
Сколько времени занимает код, использующий это в настоящее время? Сколько времени нужно, чтобы быть достаточно быстрым для ваших целей? Какие у вас есть признаки того, что в настоящее время это является узким местом в производительности вашего приложения?   -  person Servy    schedule 12.08.2013
comment
@lukegravitt Я делал это раньше, ничего не выиграл/не потерял @ Servy Несколько минут, я посмотрю на результаты профайлера   -  person Cyral    schedule 12.08.2013
comment
Tile — это структура или класс?   -  person lukegravitt    schedule 12.08.2013
comment
Я бы определенно использовал ссылку на объект в памяти. Таким образом вы удалите лишние вычисления и вызовы рекурсивных методов.   -  person AlexDev    schedule 12.08.2013
comment
@AlexDev Хорошо, я сделаю это. Также обратите внимание, что референсы вызываются редко, так как количество больших плиток, таких как двери/столы, по сравнению с грязью/деревом очень мало.   -  person Cyral    schedule 12.08.2013
comment
Я бы рассмотрел ваши алгоритмы освещения/столкновения на предмет эффективности. Освещение, в частности, может быть огромной проблемой производительности. Возможно, вы вызываете методы доступа get больше раз, чем необходимо.   -  person RogerN    schedule 12.08.2013
comment
Освещение вызывается только при размещении нового блока, я просто использовал это в качестве примера, это не сильно влияет на производительность.   -  person Cyral    schedule 12.08.2013


Ответы (1)


Я бы предложил, чтобы для каждого набора квадратов, которые логически соединены вместе, вы создали экземпляр чего-то вроде:

private class TileRef { public Tile tile; public int X,Y,Width,Height;}

Затем создайте двумерный или зубчатый массив TileRef. Все квадраты в объединенной группе должны содержать ссылку на один и тот же объект TileRef. Это должно позволить вам очень быстро найти TileRef, связанный с любым квадратом на доске, без необходимости использования условной логики для работы с тайлами разного размера.

Чтобы построить сетку из плиток размера один:

TileRef[,] tiles;
TileMapper(int xsize, int ysize)
{
  tiles = new TileRef[xsize,ysize];
  for (int x=0; x < xsize; x++)
    for (int y=0; y < xsize; y++)
    {
      var thisRef = new TileRef();
      thisRef.X = x;
      thisRef.Y = y;
      thisRef.Width = 1;
      thisRef.Height = 1;
      thisRef.Tile = new Tile(); // Make a default tile instance somehow
      tiles[x][y] = thisRef;
    }
}

To join a bunch of squares together into a blob:

public JoinSquares(int x, int y, int width, int height)
{
      var thisRef = new TileRef();
      thisRef.X = x;
      thisRef.Y = y;
      thisRef.Width = 1;
      thisRef.Height = 1;
      thisRef.Tile = new Tile(); // Make a default tile instance somehow
      for (i=0; i<width; i++)
        for (j=0; j<height; j++)
          tiles[x+i,y+j] = thisRef;
}

public SeparateSquares(int x, int y)
{
      var oldRef = tiles[x,y];
      x=oldref.X;
      y=oldref.Y;
      var width=oldref.Width;
      var height=oldref.Height;

      for (i=0; i<width; i++)
        for (j=0; j<height; j++)
        {
          var thisRef = new TileRef();
          thisRef.X = x+i;
          thisRef.Y = y+j;
          thisRef.Width = 1;
          thisRef.Height = 1;
          thisRef.Tile = new Tile(); // Make a default tile instance somehow
          tiles[x+i,y+j] = thisRef;
        }
}

To change the `Tile` associated with a "blob", simply

public Tile this[int x, int y]
{
    get 
    {
        return tiles[x,y].Tile;
    }
    set
    {
        tiles[x,y].Tile = value;
    }
}

Циклы необходимы для соединения квадратов или их разделения, но не для простого изменения атрибутов, связанных с большим двоичным объектом, путем изменения его свойства Tile.

person supercat    schedule 12.08.2013
comment
Я думаю, вы могли бы просто иметь ссылку на основной Tile в самом классе Tile, и он просто указывал бы на себя, если IsLarge ложно. Нет необходимости во втором наборе данных. - person lukegravitt; 12.08.2013
comment
@lukegravitt: второй уровень косвенности необходим, если кто-то хочет, чтобы метод set можно было использовать указанным образом. В противном случае, если есть плитка 3x3 с верхним левым углом в (3,6), попытка вызова set по координате (4,7) приведет к тому, что этот квадрат будет указывать на новый объект, а окружающие квадраты будут указывать на старый. - person supercat; 12.08.2013
comment
В этом случае метод set определенно станет более сложным, но вам не обязательно нужен следующий уровень. Вы можете получить указанный Tile и перебрать его расширения, при необходимости настроив. Однако этот вариант использования имеет смысл, поскольку вы эффективно разбиваете группу, поэтому необходимо посетить каждую плитку. - person lukegravitt; 12.08.2013
comment
@lukegravitt: если сохранение Tile в местоположении должно обновить все части большей плитки и нет дополнительного уровня косвенности, объект сопоставления должен будет знать, какие квадраты нуждаются в обновлении. Информация, используемая для этой цели, должна принадлежать объекту сопоставления; ничто другое не должно быть в состоянии изменить его. Добавление второго уровня косвенности устраняет необходимость в цикле и позволяет объекту сопоставления сохранять конфиденциальность своей информации. - person supercat; 12.08.2013
comment
Вам все равно понадобится цикл с вашим решением. Впрочем, я с вами согласен. Вся эта настройка была бы намного эффективнее, если бы для плиток использовалось значение struct, а информация о смежности сохранялась в отдельной структуре, особенно с учетом того, что эти большие плитки довольно редки, как указано в вопросе. - person lukegravitt; 12.08.2013
comment
@lukegravitt: Цикл понадобится только в тех случаях, когда вы изменяете размер квадрата (что следует делать в методе, а не в установщике свойств). В противном случае запись в tilerefs[x,y].tile за одну операцию изменит tile, связанную со всеми местоположениями сетки, которые следует изменить. - person supercat; 12.08.2013
comment
Если вы можете показать мне код, чтобы сделать то, что вы предлагаете, без цикла, я буду впечатлен. - person lukegravitt; 12.08.2013