实现 IQueryable 和 AST 以在游戏中查找实体
Implementing IQueryable and ASTs for finding entities in a game
据我所知,在某些企业 C# 代码中,有一种方法可以将 LINQ 查询转换为 AST,然后使用 IQueryable
和 [=12= 将其转换为 SQL 或类似的东西].
这在我看来是这样的:
代码忽略了数据库实现 -> 黑魔法 -> 优化 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
lambda。
很明显,如果 Where
lambda 仅在位置在 208 < x < 256 和 192 < y < 224 范围内的块上执行,则可以优化此代码。
有没有办法智能地解释 LINQ 并执行此优化?我能以某种方式实现 IQueryable
并以执行一些黑魔法的方式使用 Expression
吗?
很抱歉,如果我没有任何意义,但我不明白如何将类似于上述查询的 LINQ 查询转换为高效 SQL。
好吧,我发题半小时后当然想通了
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
时,lambda 表达式变成了不同于分配给委托的东西。
据我所知,在某些企业 C# 代码中,有一种方法可以将 LINQ 查询转换为 AST,然后使用 IQueryable
和 [=12= 将其转换为 SQL 或类似的东西].
这在我看来是这样的:
代码忽略了数据库实现 -> 黑魔法 -> 优化 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
lambda。
很明显,如果 Where
lambda 仅在位置在 208 < x < 256 和 192 < y < 224 范围内的块上执行,则可以优化此代码。
有没有办法智能地解释 LINQ 并执行此优化?我能以某种方式实现 IQueryable
并以执行一些黑魔法的方式使用 Expression
吗?
很抱歉,如果我没有任何意义,但我不明白如何将类似于上述查询的 LINQ 查询转换为高效 SQL。
好吧,我发题半小时后当然想通了
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
时,lambda 表达式变成了不同于分配给委托的东西。