Use MobileInfo.GetMovementClass for domain indexing, clearing path for caching and smarter behavior

* Move DomainIndex from being a manual hard-coded hook in World to an IWorldLoaded trait.
This commit is contained in:
Andrew Aldridge
2013-06-26 23:38:01 -04:00
parent 6fb01c7ab8
commit ba885907ba
9 changed files with 169 additions and 119 deletions

View File

@@ -1,106 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Effects;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Orders;
using OpenRA.Support;
using OpenRA.Traits;
using XRandom = OpenRA.Thirdparty.Random;
namespace OpenRA
{
public class DomainIndex
{
Rectangle bounds;
int[,] domains;
public DomainIndex(World world)
{
bounds = world.Map.Bounds;
domains = new int[(bounds.Width + bounds.X), (bounds.Height + bounds.Y)];
BuildDomains(world);
}
public int GetDomainOf(CPos p)
{
return domains[p.X, p.Y];
}
public bool IsCrossDomain(CPos p1, CPos p2)
{
return GetDomainOf(p1) != GetDomainOf(p2);
}
public void SetDomain(CPos p, int domain)
{
domains[p.X, p.Y] = domain;
}
void BuildDomains(World world)
{
Map map = world.Map;
int i = 1;
HashSet<CPos> unassigned = new HashSet<CPos>();
// Fill up our set of yet-unassigned map cells
for (int x = map.Bounds.Left; x < bounds.Right; x += 1)
{
for (int y = bounds.Top; y < bounds.Bottom; y += 1)
{
unassigned.Add(new CPos(x, y));
}
}
while (unassigned.Count != 0)
{
var start = unassigned.First();
unassigned.Remove(start);
// Wander around looking for water transitions
bool inWater = WorldUtils.GetTerrainInfo(world, start).IsWater;
Queue<CPos> toProcess = new Queue<CPos>();
HashSet<CPos> seen = new HashSet<CPos>();
toProcess.Enqueue(start);
do
{
CPos p = toProcess.Dequeue();
if (seen.Contains(p)) continue;
seen.Add(p);
TerrainTypeInfo cellInfo = WorldUtils.GetTerrainInfo(world, p);
bool isWater = cellInfo.IsWater;
// Check if we're still in one contiguous domain
if (inWater == isWater)
{
SetDomain(p, i);
unassigned.Remove(p);
// Visit our neighbors, if we haven't already
foreach (var d in CVec.directions)
{
CPos nextPos = p + d;
if (nextPos.X >= map.Bounds.Left && nextPos.Y >= map.Bounds.Top &&
nextPos.X < map.Bounds.Right && nextPos.Y < map.Bounds.Bottom)
{
if (!seen.Contains(nextPos)) toProcess.Enqueue(nextPos);
}
}
}
} while (toProcess.Count != 0);
i += 1;
}
Log.Write("debug", "{0}: Found {1} domains", map.Title, i-1);
}
}
}

View File

@@ -85,7 +85,6 @@
<Compile Include="ActorInitializer.cs" />
<Compile Include="ActorMap.cs" />
<Compile Include="ActorReference.cs" />
<Compile Include="DomainIndex.cs" />
<Compile Include="Graphics\QuadRenderer.cs" />
<Compile Include="PSubVec.cs" />
<Compile Include="PSubPos.cs" />

View File

@@ -111,7 +111,6 @@ namespace OpenRA
}
}
public DomainIndex WorldDomains;
internal World(Manifest manifest, Map map, OrderManager orderManager, bool isShellmap)
{
IsShellmap = isShellmap;
@@ -122,9 +121,6 @@ namespace OpenRA
TileSet = Rules.TileSets[Map.Tileset];
TileSet.LoadTiles();
// Identify untraversable regions of the map for faster pathfinding, especially with AI
WorldDomains = new DomainIndex(this);
SharedRandom = new XRandom(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
WorldActor = CreateActor( "World", new TypeDictionary() );

View File

@@ -44,9 +44,6 @@ namespace OpenRA.Mods.RA.Move
{
using (new PerfSample("Pathfinder"))
{
// If a water-land transition is required, bail early
if (world.WorldDomains.IsCrossDomain(from, target)) return new List<CPos>(0);
var cached = CachedPaths.FirstOrDefault(p => p.from == from && p.to == target && p.actor == self);
if (cached != null)
{
@@ -58,6 +55,15 @@ namespace OpenRA.Mods.RA.Move
var mi = self.Info.Traits.Get<MobileInfo>();
// If a water-land transition is required, bail early
var domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>();
if (domainIndex != null)
{
var passable = mi.GetMovementClass(world.TileSet);
if (!domainIndex.IsPassable(from, target, (uint)passable))
return new List<CPos>(0);
}
var pb = FindBidiPath(
PathSearch.FromPoint(world, mi, self, target, from, true),
PathSearch.FromPoint(world, mi, self, from, target, true).InReverse()
@@ -87,12 +93,17 @@ namespace OpenRA.Mods.RA.Move
// This assumes that the SubCell does not change during the path traversal
var tilesInRange = world.FindTilesInCircle(targetCell, range.Range / 1024 + 1)
.Where(t => (t.CenterPosition - target).LengthSquared <= rangeSquared
&& mi.CanEnterCell(self.World, self, t, null, true, true)
&& !world.WorldDomains.IsCrossDomain(src, t));
&& mi.CanEnterCell(self.World, self, t, null, true, true));
if(tilesInRange.Count() == 0)
// See if there is any cell within range that does not involve a cross-domain request
// Really, we only need to check the circle perimeter, but it's not clear that would be a performance win
var domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>();
if (domainIndex != null)
{
return new List<CPos>(0);
var passable = mi.GetMovementClass(world.TileSet);
tilesInRange = new List<CPos>(tilesInRange.Where(t => domainIndex.IsPassable(src, t, (uint)passable)));
if (tilesInRange.Count() == 0)
return new List<CPos>(0);
}
var path = FindBidiPath(

View File

@@ -457,6 +457,7 @@
<Compile Include="Widgets\SlidingContainerWidget.cs" />
<Compile Include="Widgets\ResourceBarWidget.cs" />
<Compile Include="Widgets\Logic\SimpleTooltipLogic.cs" />
<Compile Include="World\DomainIndex.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -0,0 +1,146 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Effects;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Orders;
using OpenRA.Support;
using OpenRA.Traits;
using OpenRA.Mods.RA.Move;
using XRandom = OpenRA.Thirdparty.Random;
namespace OpenRA.Mods.RA
{
// Identify untraversable regions of the map for faster pathfinding, especially with AI
class DomainIndexInfo : TraitInfo<DomainIndex> {}
public class DomainIndex : IWorldLoaded
{
Dictionary<uint, MovementClassDomainIndex> domains;
public void WorldLoaded(World world)
{
domains = new Dictionary<uint, MovementClassDomainIndex>();
var movementClasses = new HashSet<uint>(
Rules.Info.Where(ai => ai.Value.Traits.Contains<MobileInfo>())
.Select(ai => (uint)ai.Value.Traits.Get<MobileInfo>().GetMovementClass(world.TileSet)));
foreach(var mc in movementClasses) domains[mc] = new MovementClassDomainIndex(world, mc);
}
public bool IsPassable(CPos p1, CPos p2, uint movementClass)
{
return domains[movementClass].IsPassable(p1, p2);
}
}
class MovementClassDomainIndex
{
Rectangle bounds;
uint movementClass;
int[,] domains;
public MovementClassDomainIndex(World world, uint movementClass)
{
this.movementClass = movementClass;
bounds = world.Map.Bounds;
domains = new int[(bounds.Width + bounds.X), (bounds.Height + bounds.Y)];
BuildDomains(world);
}
public int GetDomainOf(CPos p)
{
return domains[p.X, p.Y];
}
public bool IsPassable(CPos p1, CPos p2)
{
return domains[p1.X, p1.Y] == domains[p2.X, p2.Y];
}
public void SetDomain(CPos p, int domain)
{
domains[p.X, p.Y] = domain;
}
void BuildDomains(World world)
{
Map map = world.Map;
int i = 1;
var unassigned = new HashSet<CPos>();
// Fill up our set of yet-unassigned map cells
for (int x = map.Bounds.Left; x < bounds.Right; x += 1)
{
for (int y = bounds.Top; y < bounds.Bottom; y += 1)
{
unassigned.Add(new CPos(x, y));
}
}
while (unassigned.Count != 0)
{
var start = unassigned.First();
unassigned.Remove(start);
// Wander around looking for water transitions
string currentTileType = WorldUtils.GetTerrainType(world, start);
int terrainOffset = world.TileSet.Terrain.OrderBy(t => t.Key).ToList().FindIndex(x => x.Key == currentTileType);
bool currentPassable = (movementClass & (1 << terrainOffset)) > 0;
var toProcess = new Queue<CPos>();
var seen = new HashSet<CPos>();
toProcess.Enqueue(start);
do
{
CPos p = toProcess.Dequeue();
if (seen.Contains(p)) continue;
seen.Add(p);
string candidateTileType = WorldUtils.GetTerrainType(world, p);
int candidateTerrainOffset = world.TileSet.Terrain.OrderBy(t => t.Key).ToList().FindIndex(x => x.Key == candidateTileType);
bool candidatePassable = (movementClass & (1 << candidateTerrainOffset)) > 0;
// Check if we're still in one contiguous domain
if (currentPassable == candidatePassable)
{
SetDomain(p, i);
unassigned.Remove(p);
// Visit our neighbors, if we haven't already
foreach (var d in CVec.directions)
{
CPos nextPos = p + d;
if (nextPos.X >= map.Bounds.Left && nextPos.Y >= map.Bounds.Top &&
nextPos.X < map.Bounds.Right && nextPos.Y < map.Bounds.Bottom)
{
if (!seen.Contains(nextPos)) toProcess.Enqueue(nextPos);
}
}
}
} while (toProcess.Count != 0);
i += 1;
}
Log.Write("debug", "{0}: Found {1} domains", map.Title, i-1);
}
}
}

View File

@@ -158,7 +158,7 @@ Player:
silo: 1
BuildingFractions:
proc: 17%
nuke: 10%
nuke: 10%
pyle: 7%
hand: 9%
hq: 1%
@@ -273,6 +273,7 @@ World:
ProductionQueueFromSelection:
ProductionTabsWidget: PRODUCTION_TABS
BibLayer:
DomainIndex:
ResourceLayer:
ResourceClaimLayer:
ResourceType@green-tib:

View File

@@ -354,6 +354,7 @@ World:
BibLayer:
BibTypes: bib3x, bib2x
BibWidths: 3, 2
DomainIndex:
ResourceLayer:
ResourceClaimLayer:
ResourceType@Spice:

View File

@@ -604,6 +604,7 @@ World:
Name: Soviet
Race: soviet
BibLayer:
DomainIndex:
ResourceLayer:
ResourceClaimLayer:
ResourceType@ore: