Реализация IQueryable и AST для поиска сущностей в игре - PullRequest
0 голосов
/ 30 апреля 2018

Насколько я понимаю, в некотором корпоративном коде C # есть способ преобразовать LINQ-запросы в AST, которые затем переводятся в SQL или что-то подобное, используя IQueryable и Expression.
Это выглядит для меня так:

Код, не обращающий внимания на реализацию БД -> Черная магия -> Оптимизированный SQL

Я бы хотел понять эту черную магию и применить ее к игре. Это мой сценарий:

class Entity { public Vector2 position; }

class Chunk {
   const int CHUNK_SIZE = 16;
   public Vector2 position;  // chunk position is multiple of CHUNK_SIZE
   public List<Entity> entities;
}

class World {
   public Chunk[,] chunks; // Let's imagine this is a 256x256 array of chunks.
   public IEnumerable<Entity> Entities {
      get {
         return chunks.SelectMany(c => c.entities);
      }
   }
}

class SomewhereElse {
   void NotVerySmartCode() {
      var someArbitraryEntities = world.Entities
         .Where(e => e.position.x > 213 && e.position.x < 247
            && e.position.y > 198 && e.position.y < 212);

      foreach (var e in someArbitraryEntities) { // slooooooow }
   }
}

Когда NotVerySmartCode запрашивает World.Entities, перечислитель обходит все чанки и все сущности и выполняет лямбду Where для каждого из них.

Совершенно очевидно, что этот код можно было бы оптимизировать, если бы лямбда Where выполнялась только на кусках, положение которых было в пределах 208

Есть ли способ интеллектуальной интерпретации LINQ и выполнения этой оптимизации? Могу ли я как-то реализовать IQueriable и использовать Expression таким образом, чтобы выполнить какую-то чёрную магию?

Извините, если я не понимаю, но я не понимаю, как LINQ-запросы, аналогичные приведенным выше, могут быть преобразованы в эффективный SQL.

1 Ответ

0 голосов
/ 30 апреля 2018

Хорошо, конечно, я понял это через полчаса после публикации вопроса.

Func<int, int> addThree = (a) => a + 3; // This compiles to IL
Expression<Func<int, int>> addTwo = (a) => a + 2; // This compiles to an AST

Мы можем перемещаться по AST и интерпретировать его:

Debug.Log(addTwo.Body); // (a + 2)
Debug.Log(addTwo.Type); // Func<int, int>
Debug.Log(addTwo.Body.NodeType); // Add
Debug.Log(addTwo.Body is BinaryExpression); // True
Debug.Log(((BinaryExpression)addTwo.Body).Left.NodeType); // Parameter
Debug.Log(((BinaryExpression)addTwo.Body).Right.NodeType); // Constant
Debug.Log(((ConstantExpression)((BinaryExpression)addTwo.Body).Right).Value); // 2

Мы можем скомпилировать AST:

return addTwo.Compile()(2); // 4

Мы также могли бы теоретически обнаружить шаблоны и оптимизировать их.
Или мы могли бы перевести их на SQL.

По сути, мне не хватало информации о том, что лямбда-выражение при присвоении Expression становится чем-то иным, чем при назначении делегату.

...