RangeManager: Grid for 2D array, enum cleanups.

Range manager has several `std::vector` for fixed-size arrays and 2D
grids. By using proper data structures, the code readability is
improved.
This also moves around the LosVisibility enum.

Comments by: Stan`, nani
Differential Revision: https://code.wildfiregames.com/D2770
This was SVN commit r23769.
This commit is contained in:
wraitii 2020-06-13 09:05:40 +00:00
parent 6f70a901f8
commit 939002f0dc
14 changed files with 453 additions and 313 deletions

View File

@ -249,7 +249,7 @@ void CCameraController::Update(const float deltaRealTime)
CmpPtr<ICmpPosition> cmpPosition(*(g_Game->GetSimulation2()), m_FollowEntity);
CmpPtr<ICmpRangeManager> cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY);
if (cmpPosition && cmpPosition->IsInWorld() &&
cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FollowEntity, g_Game->GetViewedPlayerID()) == ICmpRangeManager::VIS_VISIBLE)
cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FollowEntity, g_Game->GetViewedPlayerID()) == LosVisibility::VISIBLE)
{
// Get the most recent interpolated position
float frameOffset = g_Game->GetSimulation2()->GetLastFrameOffset();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -41,12 +41,16 @@ public:
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 2
};
std::vector<u32> inputDataVec(inputData, inputData+size*size);
Grid<u32> inputDataVec(size, size);
// LOS_MASK should be cmpRanageManager->GetSharedLosMask(1),
for (u8 i = 0; i < size; ++i)
for (u8 j = 0; j < size; ++j)
inputDataVec.set(i, j, inputData[i + j * size]);
// LosState::MASK should be cmpRanageManager->GetSharedLosMask(1),
// but that would mean adding a huge mock component for this and it
// should always be LOS_MASK for player 1 (as the other players are bit-shifted).
ICmpRangeManager::CLosQuerier los(ICmpRangeManager::LOS_MASK, inputDataVec, size);
// should always be LosState::MASK for player 1 (as the other players are bit-shifted).
ICmpRangeManager::CLosQuerier los((u32)LosState::MASK, inputDataVec, size);
std::vector<u8> losData;
size_t pitch;
@ -66,13 +70,12 @@ public:
CLOSTexture tex(sim);
const ssize_t size = 257;
std::vector<u32> inputDataVec;
inputDataVec.resize(size*size);
Grid<u32> inputDataVec(size, size);
// LOS_MASK should be cmpRanageManager->GetSharedLosMask(1),
// LosState::MASK should be cmpRanageManager->GetSharedLosMask(1),
// but that would mean adding a huge mock component for this and it
// should always be LOS_MASK for player 1 (as the other players are bit-shifted).
ICmpRangeManager::CLosQuerier los(ICmpRangeManager::LOS_MASK, inputDataVec, size);
// should always be LosState::MASK for player 1 (as the other players are bit-shifted).
ICmpRangeManager::CLosQuerier los((u32)LosState::MASK, inputDataVec, size);
size_t reps = 128;
double t = timer_Time();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -544,8 +544,8 @@ void CMiniMap::Draw()
ICmpMinimap* cmpMinimap = static_cast<ICmpMinimap*>(it->second);
if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
{
ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer());
if (vis != ICmpRangeManager::VIS_HIDDEN)
LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer());
if (vis != LosVisibility::HIDDEN)
{
v.a = 255;
v.x = posX.ToFloat() * sx;

View File

@ -46,22 +46,6 @@
#define LOS_TILES_RATIO 8
#define DEBUG_RANGE_MANAGER_BOUNDS 0
/**
* Representation of a range query.
*/
struct Query
{
bool enabled;
bool parabolic;
CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
entity_pos_t minRange;
entity_pos_t maxRange;
entity_pos_t elevationBonus;
u32 ownersMask;
i32 interface;
std::vector<entity_id_t> lastMatch;
u8 flagsMask;
};
/**
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
@ -81,7 +65,7 @@ static inline u32 CalcOwnerMask(player_id_t owner)
static inline u32 CalcPlayerLosMask(player_id_t player)
{
if (player > 0 && player <= 16)
return ICmpRangeManager::LOS_MASK << (2*(player-1));
return (u32)LosState::MASK << (2*(player-1));
return 0;
}
@ -119,11 +103,11 @@ static bool SetPlayerSharedDirtyVisibilityBit(u16& mask, player_id_t player, boo
/**
* Computes the 2-bit visibility for one player, given the total 32-bit visibilities
*/
static inline u8 GetPlayerVisibility(u32 visibilities, player_id_t player)
static inline LosVisibility GetPlayerVisibility(u32 visibilities, player_id_t player)
{
if (player > 0 && player <= 16)
return (visibilities >> (2 *(player-1))) & 0x3;
return 0;
return static_cast<LosVisibility>( (visibilities >> (2 *(player-1))) & 0x3 );
return LosVisibility::HIDDEN;
}
/**
@ -152,6 +136,23 @@ static inline u16 CalcVisionSharingMask(player_id_t player)
return 1 << (player-1);
}
/**
* Representation of a range query.
*/
struct Query
{
bool enabled;
bool parabolic;
CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
entity_pos_t minRange;
entity_pos_t maxRange;
entity_pos_t elevationBonus;
u32 ownersMask;
i32 interface;
std::vector<entity_id_t> lastMatch;
u8 flagsMask;
};
/**
* Checks whether v is in a parabolic range of (0,0,0)
* The highest point of the paraboloid is (0,range/2,0)
@ -372,16 +373,18 @@ public:
// LOS state:
static const player_id_t MAX_LOS_PLAYER_ID = 16;
std::vector<bool> m_LosRevealAll;
using LosTile = std::pair<u16, u16>;
std::array<bool, MAX_LOS_PLAYER_ID+2> m_LosRevealAll;
bool m_LosCircular;
i32 m_TerrainVerticesPerSide;
// Cache for visibility tracking
i32 m_LosTilesPerSide;
bool m_GlobalVisibilityUpdate;
std::vector<u8> m_GlobalPlayerVisibilityUpdate;
std::vector<u16> m_DirtyVisibility;
std::vector<std::set<entity_id_t> > m_LosTiles;
std::array<bool, MAX_LOS_PLAYER_ID> m_GlobalPlayerVisibilityUpdate;
Grid<u16> m_DirtyVisibility;
Grid<std::set<entity_id_t>> m_LosTiles;
// List of entities that must be updated, regardless of the status of their tile
std::vector<entity_id_t> m_ModifiedEntities;
@ -390,19 +393,19 @@ public:
// of units in a very small area.
// (Note we use vertexes, not tiles, to better match the renderer.)
// Lazily constructed when it's needed, to save memory in smaller games.
std::vector<std::vector<u16> > m_LosPlayerCounts;
std::array<Grid<u16>, MAX_LOS_PLAYER_ID> m_LosPlayerCounts;
// 2-bit ELosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
std::vector<u32> m_LosState;
// 2-bit LosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
Grid<u32> m_LosState;
// Special static visibility data for the "reveal whole map" mode
// (TODO: this is usually a waste of memory)
std::vector<u32> m_LosStateRevealed;
Grid<u32> m_LosStateRevealed;
// Shared LOS masks, one per player.
std::vector<u32> m_SharedLosMasks;
std::array<u32, MAX_LOS_PLAYER_ID+2> m_SharedLosMasks;
// Shared dirty visibility masks, one per player.
std::vector<u16> m_SharedDirtyVisibilityMasks;
std::array<u16, MAX_LOS_PLAYER_ID+2> m_SharedDirtyVisibilityMasks;
// Cache explored vertices per player (not serialized)
u32 m_TotalInworldVertices;
@ -431,13 +434,9 @@ public:
// The whole map should be visible to Gaia by default, else e.g. animals
// will get confused when trying to run from enemies
m_LosRevealAll.resize(MAX_LOS_PLAYER_ID+2,false);
m_LosRevealAll[0] = true;
m_SharedLosMasks.resize(MAX_LOS_PLAYER_ID+2,0);
m_SharedDirtyVisibilityMasks.resize(MAX_LOS_PLAYER_ID + 2, 0);
m_GlobalVisibilityUpdate = true;
m_GlobalPlayerVisibilityUpdate.resize(MAX_LOS_PLAYER_ID);
m_LosCircular = false;
m_TerrainVerticesPerSide = 0;
@ -459,22 +458,22 @@ public:
SerializeMap<SerializeU32_Unbounded, SerializeQuery>()(serialize, "queries", m_Queries, GetSimContext());
SerializeEntityMap<SerializeEntityData>()(serialize, "entity data", m_EntityData);
SerializeVector<SerializeBool>()(serialize, "los reveal all", m_LosRevealAll);
SerializeArray<SerializeBool>()(serialize, "los reveal all", m_LosRevealAll);
serialize.Bool("los circular", m_LosCircular);
serialize.NumberI32_Unbounded("terrain verts per side", m_TerrainVerticesPerSide);
serialize.Bool("global visibility update", m_GlobalVisibilityUpdate);
SerializeVector<SerializeU8_Unbounded>()(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate);
SerializeRepetitiveVector<SerializeU16_Unbounded>()(serialize, "dirty visibility", m_DirtyVisibility);
SerializeArray<SerializeBool>()(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate);
SerializedGridCompressed<SerializeU16_Unbounded>()(serialize, "dirty visibility", m_DirtyVisibility);
SerializeVector<SerializeU32_Unbounded>()(serialize, "modified entities", m_ModifiedEntities);
// We don't serialize m_Subdivision, m_LosPlayerCounts or m_LosTiles
// since they can be recomputed from the entity data when deserializing;
// m_LosState must be serialized since it depends on the history of exploration
SerializeRepetitiveVector<SerializeU32_Unbounded>()(serialize, "los state", m_LosState);
SerializeVector<SerializeU32_Unbounded>()(serialize, "shared los masks", m_SharedLosMasks);
SerializeVector<SerializeU16_Unbounded>()(serialize, "shared dirty visibility masks", m_SharedDirtyVisibilityMasks);
SerializedGridCompressed<SerializeU32_Unbounded>()(serialize, "los state", m_LosState);
SerializeArray<SerializeU32_Unbounded>()(serialize, "shared los masks", m_SharedLosMasks);
SerializeArray<SerializeU16_Unbounded>()(serialize, "shared dirty visibility masks", m_SharedDirtyVisibilityMasks);
}
virtual void Serialize(ISerializer& serialize)
@ -563,8 +562,8 @@ public:
SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to);
else
LosMove(it->second.owner, it->second.visionRange, from, to);
i32 oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z);
i32 newLosTile = PosToLosTilesHelper(msgData.x, msgData.z);
LosTile oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z);
LosTile newLosTile = PosToLosTilesHelper(msgData.x, msgData.z);
if (oldLosTile != newLosTile)
{
RemoveFromTile(oldLosTile, ent);
@ -782,10 +781,10 @@ public:
// Check that calling ResetDerivedData (i.e. recomputing all the state from scratch)
// does not affect the incrementally-computed state
std::vector<std::vector<u16> > oldPlayerCounts = m_LosPlayerCounts;
std::vector<u32> oldStateRevealed = m_LosStateRevealed;
std::array<Grid<u16>, MAX_LOS_PLAYER_ID> oldPlayerCounts = m_LosPlayerCounts;
Grid<u32> oldStateRevealed = m_LosStateRevealed;
FastSpatialSubdivision oldSubdivision = m_Subdivision;
std::vector<std::set<entity_id_t> > oldLosTiles = m_LosTiles;
Grid<std::set<entity_id_t> > oldLosTiles = m_LosTiles;
m_Deserializing = true;
ResetDerivedData();
@ -793,19 +792,25 @@ public:
if (oldPlayerCounts != m_LosPlayerCounts)
{
for (size_t i = 0; i < oldPlayerCounts.size(); ++i)
for (size_t id = 0; id < m_LosPlayerCounts.size(); ++id)
{
debug_printf("%d: ", (int)i);
for (size_t j = 0; j < oldPlayerCounts[i].size(); ++j)
debug_printf("%d ", oldPlayerCounts[i][j]);
debug_printf("\n");
debug_printf("player %li\n", id);
for (size_t i = 0; i < oldPlayerCounts[id].width(); ++i)
{
for (size_t j = 0; j < oldPlayerCounts[id].height(); ++j)
debug_printf("%i ", oldPlayerCounts[id].get(i,j));
debug_printf("\n");
}
}
for (size_t i = 0; i < m_LosPlayerCounts.size(); ++i)
for (size_t id = 0; id < m_LosPlayerCounts.size(); ++id)
{
debug_printf("%d: ", (int)i);
for (size_t j = 0; j < m_LosPlayerCounts[i].size(); ++j)
debug_printf("%d ", m_LosPlayerCounts[i][j]);
debug_printf("\n");
debug_printf("player %li\n", id);
for (size_t i = 0; i < m_LosPlayerCounts[id].width(); ++i)
{
for (size_t j = 0; j < m_LosPlayerCounts[id].height(); ++j)
debug_printf("%i ", m_LosPlayerCounts[id].get(i,j));
debug_printf("\n");
}
}
debug_warn(L"inconsistent player counts");
}
@ -830,10 +835,12 @@ public:
m_LosTilesPerSide = (m_TerrainVerticesPerSide - 1)/LOS_TILES_RATIO;
m_LosPlayerCounts.clear();
m_LosPlayerCounts.resize(MAX_LOS_PLAYER_ID+1);
for (size_t player_id = 0; player_id < m_LosPlayerCounts.size(); ++player_id)
m_LosPlayerCounts[player_id].reset();
m_ExploredVertices.clear();
m_ExploredVertices.resize(MAX_LOS_PLAYER_ID+1, 0);
if (m_Deserializing)
{
// recalc current exploration stats.
@ -841,25 +848,20 @@ public:
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++)
if (!LosIsOffWorld(i, j))
for (u8 k = 1; k < MAX_LOS_PLAYER_ID+1; ++k)
m_ExploredVertices.at(k) += ((m_LosState[j*m_TerrainVerticesPerSide + i] & (LOS_EXPLORED << (2*(k-1)))) > 0);
}
else
{
m_LosState.clear();
m_LosState.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
}
m_LosStateRevealed.clear();
m_LosStateRevealed.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
m_ExploredVertices.at(k) += ((m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(k-1)))) > 0);
} else
m_LosState.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
m_LosStateRevealed.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
if (!m_Deserializing)
{
m_DirtyVisibility.clear();
m_DirtyVisibility.resize(m_LosTilesPerSide*m_LosTilesPerSide);
m_DirtyVisibility.resize(m_LosTilesPerSide, m_LosTilesPerSide);
}
ENSURE(m_DirtyVisibility.size() == (size_t)(m_LosTilesPerSide*m_LosTilesPerSide));
ENSURE(m_DirtyVisibility.width() == m_LosTilesPerSide);
ENSURE(m_DirtyVisibility.height() == m_LosTilesPerSide);
m_LosTiles.clear();
m_LosTiles.resize(m_LosTilesPerSide*m_LosTilesPerSide);
m_LosTiles.resize(m_LosTilesPerSide, m_LosTilesPerSide);
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
if (it->second.HasFlag<FlagMasks::InWorld>())
@ -879,10 +881,10 @@ public:
for (ssize_t i = 0; i < m_TerrainVerticesPerSide; ++i)
{
if (LosIsOffWorld(i,j))
m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = 0;
m_LosStateRevealed.get(i, j) = 0;
else
{
m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = 0xFFFFFFFFu;
m_LosStateRevealed.get(i, j) = 0xFFFFFFFFu;
m_TotalInworldVertices++;
}
}
@ -1582,19 +1584,19 @@ public:
it->second.SetFlag<FlagMasks::ScriptedVisibility>(status);
}
ELosVisibility ComputeLosVisibility(CEntityHandle ent, player_id_t player) const
LosVisibility ComputeLosVisibility(CEntityHandle ent, player_id_t player) const
{
// Entities not with positions in the world are never visible
if (ent.GetId() == INVALID_ENTITY)
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
CmpPtr<ICmpPosition> cmpPosition(ent);
if (!cmpPosition || !cmpPosition->IsInWorld())
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
// Mirage entities, whatever the situation, are visible for one specific player
CmpPtr<ICmpMirage> cmpMirage(ent);
if (cmpMirage && cmpMirage->GetPlayer() != player)
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
CFixedVector2D pos = cmpPosition->GetPosition2D();
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
@ -1604,9 +1606,8 @@ public:
if (GetLosRevealAll(player))
{
if (LosIsOffWorld(i, j) || cmpMirage)
return VIS_HIDDEN;
else
return VIS_VISIBLE;
return LosVisibility::HIDDEN;
return LosVisibility::VISIBLE;
}
// Get visible regions
@ -1632,41 +1633,41 @@ public:
if (los.IsVisible(i, j))
{
if (cmpMirage)
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
return VIS_VISIBLE;
return LosVisibility::VISIBLE;
}
if (!los.IsExplored(i, j))
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
// Invisible if the 'retain in fog' flag is not set, and in a non-visible explored region
// Try using the 'retainInFog' flag in m_EntityData to save a script call
if (it != m_EntityData.end())
{
if (!it->second.HasFlag<FlagMasks::RetainInFog>())
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
}
else
{
if (!(cmpVisibility && cmpVisibility->GetRetainInFog()))
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
}
if (cmpMirage)
return VIS_FOGGED;
return LosVisibility::FOGGED;
CmpPtr<ICmpOwnership> cmpOwnership(ent);
if (!cmpOwnership)
return VIS_FOGGED;
return LosVisibility::FOGGED;
if (cmpOwnership->GetOwner() == player)
{
CmpPtr<ICmpFogging> cmpFogging(ent);
if (!(cmpFogging && cmpFogging->IsMiraged(player)))
return VIS_FOGGED;
return LosVisibility::FOGGED;
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
}
// Fogged entities are hidden in two cases:
@ -1675,37 +1676,36 @@ public:
CmpPtr<ICmpFogging> cmpFogging(ent);
if (cmpFogging && cmpFogging->IsActivated() &&
(!cmpFogging->WasSeen(player) || cmpFogging->IsMiraged(player)))
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
return VIS_FOGGED;
return LosVisibility::FOGGED;
}
ELosVisibility ComputeLosVisibility(entity_id_t ent, player_id_t player) const
LosVisibility ComputeLosVisibility(entity_id_t ent, player_id_t player) const
{
CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent);
return ComputeLosVisibility(handle, player);
}
virtual ELosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player) const
virtual LosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player) const
{
entity_id_t entId = ent.GetId();
// Entities not with positions in the world are never visible
if (entId == INVALID_ENTITY)
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
CmpPtr<ICmpPosition> cmpPosition(ent);
if (!cmpPosition || !cmpPosition->IsInWorld())
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
// Gaia and observers do not have a visibility cache
if (player <= 0)
return ComputeLosVisibility(ent, player);
CFixedVector2D pos = cmpPosition->GetPosition2D();
i32 n = PosToLosTilesHelper(pos.X, pos.Y);
if (IsVisibilityDirty(m_DirtyVisibility[n], player))
if (IsVisibilityDirty(m_DirtyVisibility[PosToLosTilesHelper(pos.X, pos.Y)], player))
return ComputeLosVisibility(ent, player);
if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), entId) != m_ModifiedEntities.end())
@ -1715,16 +1715,16 @@ public:
if (it == m_EntityData.end())
return ComputeLosVisibility(ent, player);
return static_cast<ELosVisibility>(GetPlayerVisibility(it->second.visibilities, player));
return static_cast<LosVisibility>(GetPlayerVisibility(it->second.visibilities, player));
}
virtual ELosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const
virtual LosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const
{
CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent);
return GetLosVisibility(handle, player);
}
virtual ELosVisibility GetLosVisibilityPosition(entity_pos_t x, entity_pos_t z, player_id_t player) const
virtual LosVisibility GetLosVisibilityPosition(entity_pos_t x, entity_pos_t z, player_id_t player) const
{
int i = (x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int j = (z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
@ -1733,22 +1733,27 @@ public:
if (GetLosRevealAll(player))
{
if (LosIsOffWorld(i, j))
return VIS_HIDDEN;
return LosVisibility::HIDDEN;
else
return VIS_VISIBLE;
return LosVisibility::VISIBLE;
}
// Get visible regions
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
if (los.IsVisible(i,j))
return VIS_VISIBLE;
return LosVisibility::VISIBLE;
if (los.IsExplored(i,j))
return VIS_FOGGED;
return VIS_HIDDEN;
return LosVisibility::FOGGED;
return LosVisibility::HIDDEN;
}
i32 PosToLosTilesHelper(entity_pos_t x, entity_pos_t z) const
LosTile PosToLosTilesHelper(u16 x, u16 z) const
{
return LosTile{ Clamp(x/LOS_TILES_RATIO, 0, m_LosTilesPerSide - 1), Clamp(z/LOS_TILES_RATIO, 0, m_LosTilesPerSide - 1) };
}
LosTile PosToLosTilesHelper(entity_pos_t x, entity_pos_t z) const
{
i32 i = Clamp(
(x/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
@ -1758,15 +1763,15 @@ public:
(z/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
0,
m_LosTilesPerSide - 1);
return j*m_LosTilesPerSide + i;
return std::make_pair(i, j);
}
void AddToTile(i32 tile, entity_id_t ent)
void AddToTile(LosTile tile, entity_id_t ent)
{
m_LosTiles[tile].insert(ent);
}
void RemoveFromTile(i32 tile, entity_id_t ent)
void RemoveFromTile(LosTile tile, entity_id_t ent)
{
std::set<entity_id_t>::const_iterator tileIt = m_LosTiles[tile].find(ent);
if (tileIt != m_LosTiles[tile].end())
@ -1777,17 +1782,19 @@ public:
{
PROFILE("UpdateVisibilityData");
for (i32 n = 0; n < m_LosTilesPerSide * m_LosTilesPerSide; ++n)
{
for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
if (IsVisibilityDirty(m_DirtyVisibility[n], player) || m_GlobalPlayerVisibilityUpdate[player-1] == 1 || m_GlobalVisibilityUpdate)
for (const entity_id_t& ent : m_LosTiles[n])
UpdateVisibility(ent, player);
for (u16 i = 0; i < m_LosTilesPerSide; ++i)
for (u16 j = 0; j < m_LosTilesPerSide; ++j)
{
LosTile pos{i, j};
for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
if (IsVisibilityDirty(m_DirtyVisibility[pos], player) || m_GlobalPlayerVisibilityUpdate[player-1] == 1 || m_GlobalVisibilityUpdate)
for (const entity_id_t& ent : m_LosTiles[pos])
UpdateVisibility(ent, player);
m_DirtyVisibility[n] = 0;
}
m_DirtyVisibility[pos] = 0;
}
std::fill(m_GlobalPlayerVisibilityUpdate.begin(), m_GlobalPlayerVisibilityUpdate.end(), 0);
std::fill(m_GlobalPlayerVisibilityUpdate.begin(), m_GlobalPlayerVisibilityUpdate.end(), false);
m_GlobalVisibilityUpdate = false;
// Calling UpdateVisibility can modify m_ModifiedEntities, so be careful:
@ -1817,15 +1824,15 @@ public:
if (itEnts == m_EntityData.end())
return;
u8 oldVis = GetPlayerVisibility(itEnts->second.visibilities, player);
u8 newVis = ComputeLosVisibility(itEnts->first, player);
LosVisibility oldVis = GetPlayerVisibility(itEnts->second.visibilities, player);
LosVisibility newVis = ComputeLosVisibility(itEnts->first, player);
if (oldVis == newVis)
return;
itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2 * (player - 1))) | (newVis << 2 * (player - 1));
itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2 * (player - 1))) | ((u8)newVis << 2 * (player - 1));
CMessageVisibilityChanged msg(player, ent, oldVis, newVis);
CMessageVisibilityChanged msg(player, ent, static_cast<int>(oldVis), static_cast<int>(newVis));
GetSimContext().GetComponentManager().PostMessage(ent, msg);
}
@ -1909,8 +1916,8 @@ public:
if (LosIsOffWorld(i,j))
continue;
u32 &explored = m_ExploredVertices.at(p);
explored += !(m_LosState[i + j*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
explored += !(m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(p-1))));
m_LosState.get(i, j) |= ((u32)LosState::EXPLORED << (2*(p-1)));
}
SeeExploredEntities(p);
@ -1951,11 +1958,11 @@ public:
if (LosIsOffWorld(ti, tj))
continue;
u32& losState = m_LosState[ti + tj * m_TerrainVerticesPerSide];
if (!(losState & (LOS_EXPLORED << (2*(p-1)))))
u32& losState = m_LosState.get(ti, tj);
if (!(losState & ((u32)LosState::EXPLORED << (2*(p-1)))))
{
++explored;
losState |= (LOS_EXPLORED << (2*(p-1)));
losState |= ((u32)LosState::EXPLORED << (2*(p-1)));
}
}
}
@ -2018,9 +2025,8 @@ public:
const Grid<u16>& shoreGrid = cmpPathfinder->ComputeShoreGrid(true);
ENSURE(shoreGrid.m_W == m_TerrainVerticesPerSide-1 && shoreGrid.m_H == m_TerrainVerticesPerSide-1);
std::vector<u16>& counts = m_LosPlayerCounts.at(p);
ENSURE(!counts.empty());
u16* countsData = &counts[0];
Grid<u16>& counts = m_LosPlayerCounts.at(p);
ENSURE(!counts.blank());
for (u16 j = 0; j < shoreGrid.m_H; ++j)
for (u16 i = 0; i < shoreGrid.m_W; ++i)
@ -2031,9 +2037,9 @@ public:
// Maybe we could be more clever and don't add dummy strips of one tile
if (enable)
LosAddStripHelper(p, i, i, j, countsData);
LosAddStripHelper(p, i, i, j, counts);
else
LosRemoveStripHelper(p, i, i, j, countsData);
LosRemoveStripHelper(p, i, i, j, counts);
}
}
@ -2069,56 +2075,50 @@ public:
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
*/
inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, Grid<u16>& counts)
{
if (i1 < i0)
return;
i32 idx0 = j*m_TerrainVerticesPerSide + i0;
i32 idx1 = j*m_TerrainVerticesPerSide + i1;
u32 &explored = m_ExploredVertices.at(owner);
for (i32 idx = idx0; idx <= idx1; ++idx)
for (i32 i = i0; i <= i1; ++i)
{
// Increasing from zero to non-zero - move from unexplored/explored to visible+explored
if (counts[idx] == 0)
if (counts.get(i, j) == 0)
{
i32 i = i0 + idx - idx0;
if (!LosIsOffWorld(i, j))
{
explored += !(m_LosState[idx] & (LOS_EXPLORED << (2*(owner-1))));
m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
explored += !(m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(owner-1))));
m_LosState.get(i, j) |= (((int)LosState::VISIBLE | (u32)LosState::EXPLORED) << (2*(owner-1)));
}
MarkVisibilityDirtyAroundTile(owner, i, j);
}
ASSERT(counts[idx] < 65535);
counts[idx] = (u16)(counts[idx] + 1); // ignore overflow; the player should never have 64K units
ENSURE(counts.get(i, j) < std::numeric_limits<u16>::max());
counts.get(i, j) = (u16)(counts.get(i, j) + 1); // ignore overflow; the player should never have 64K units
}
}
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
*/
inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, Grid<u16>& counts)
{
if (i1 < i0)
return;
i32 idx0 = j*m_TerrainVerticesPerSide + i0;
i32 idx1 = j*m_TerrainVerticesPerSide + i1;
for (i32 idx = idx0; idx <= idx1; ++idx)
for (i32 i = i0; i <= i1; ++i)
{
ASSERT(counts[idx] > 0);
counts[idx] = (u16)(counts[idx] - 1);
ASSERT(counts.get(i, j) > 0);
counts.get(i, j) = (u16)(counts.get(i, j) - 1);
// Decreasing from non-zero to zero - move from visible+explored to explored
if (counts[idx] == 0)
if (counts.get(i, j) == 0)
{
// (If LosIsOffWorld then this is a no-op, so don't bother doing the check)
m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
m_LosState.get(i, j) &= ~((int)LosState::VISIBLE << (2*(owner-1)));
i32 i = i0 + idx - idx0;
MarkVisibilityDirtyAroundTile(owner, i, j);
}
}
@ -2132,10 +2132,10 @@ public:
// Mark the LoS tiles around the updated vertex
// 1: left-up, 2: right-up, 3: left-down, 4: right-down
int n1 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
int n2 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
int n3 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
int n4 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
LosTile n1 = PosToLosTilesHelper(i-1, j-1);
LosTile n2 = PosToLosTilesHelper(i-1, j);
LosTile n3 = PosToLosTilesHelper(i, j-1);
LosTile n4 = PosToLosTilesHelper(i, j);
u16 sharedDirtyVisibilityMask = m_SharedDirtyVisibilityMasks[owner];
@ -2162,13 +2162,11 @@ public:
PROFILE("LosUpdateHelper");
std::vector<u16>& counts = m_LosPlayerCounts.at(owner);
Grid<u16>& counts = m_LosPlayerCounts.at(owner);
// Lazy initialisation of counts:
if (counts.empty())
counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
u16* countsData = &counts[0];
if (counts.blank())
counts.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
// Compute the circular region as a series of strips.
// Rather than quantise pos to vertexes, we do more precise sub-tile computations
@ -2233,9 +2231,9 @@ public:
i32 i0clamp = std::max(i0, 1);
i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide-2);
if (adding)
LosAddStripHelper(owner, i0clamp, i1clamp, j, countsData);
LosAddStripHelper(owner, i0clamp, i1clamp, j, counts);
else
LosRemoveStripHelper(owner, i0clamp, i1clamp, j, countsData);
LosRemoveStripHelper(owner, i0clamp, i1clamp, j, counts);
}
}
@ -2251,13 +2249,11 @@ public:
PROFILE("LosUpdateHelperIncremental");
std::vector<u16>& counts = m_LosPlayerCounts.at(owner);
Grid<u16>& counts = m_LosPlayerCounts.at(owner);
// Lazy initialisation of counts:
if (counts.empty())
counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
u16* countsData = &counts[0];
if (counts.blank())
counts.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
// See comments in LosUpdateHelper.
// This does exactly the same, except computing the strips for
@ -2343,11 +2339,11 @@ public:
// and we can just add/remove the entire other strip
if (i1clamp_from < i0clamp_from)
{
LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, countsData);
LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, counts);
}
else if (i1clamp_to < i0clamp_to)
{
LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, countsData);
LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, counts);
}
else
{
@ -2360,10 +2356,10 @@ public:
// movement speeds), the region between them will be both added and removed,
// so we have to do the add first to avoid overflowing to -1 and triggering
// assertion failures.)
LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, countsData);
LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, countsData);
LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, countsData);
LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, countsData);
LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, counts);
LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, counts);
LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, counts);
LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, counts);
}
}
}
@ -2448,7 +2444,7 @@ public:
continue;
for (playerIt = players.begin(); playerIt != players.end(); ++playerIt)
if (m_LosState[j*m_TerrainVerticesPerSide + i] & (LOS_EXPLORED << (2*((*playerIt)-1))))
if (m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*((*playerIt)-1))))
{
exploredVertices += 1;
break;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2018 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -68,7 +68,7 @@ public:
int currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
if (!cmpRangeManager || (cmpRangeManager->GetLosVisibility(source, currentPlayer) != ICmpRangeManager::VIS_VISIBLE))
if (!cmpRangeManager || (cmpRangeManager->GetLosVisibility(source, currentPlayer) != LosVisibility::VISIBLE))
return;
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), source);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -88,7 +88,7 @@ public:
/**
* Cached LOS visibility status.
*/
ICmpRangeManager::ELosVisibility visibility;
LosVisibility visibility;
bool visibilityDirty;
/**
@ -404,7 +404,7 @@ void CCmpUnitRenderer::RenderSubmit(SceneCollector& collector, const CFrustum& f
if (unit.visibilityDirty)
UpdateVisibility(unit);
if (unit.visibility == ICmpRangeManager::VIS_HIDDEN)
if (unit.visibility == LosVisibility::HIDDEN)
continue;
if (!g_AtlasGameLoop->running && !g_RenderingOptions.GetRenderActors() && (unit.flags & ACTOR_ONLY))
@ -451,7 +451,7 @@ void CCmpUnitRenderer::UpdateVisibility(SUnit& unit) const
// (regardless of whether the LOS system thinks it's visible)
CmpPtr<ICmpVisibility> cmpVisibility(unit.entity);
if (cmpVisibility && cmpVisibility->GetAlwaysVisible())
unit.visibility = ICmpRangeManager::VIS_VISIBLE;
unit.visibility = LosVisibility::VISIBLE;
else
{
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
@ -460,12 +460,12 @@ void CCmpUnitRenderer::UpdateVisibility(SUnit& unit) const
}
}
else
unit.visibility = ICmpRangeManager::VIS_HIDDEN;
unit.visibility = LosVisibility::HIDDEN;
// Change the visibility of the visual actor's selectable if it has one.
CmpPtr<ICmpSelectable> cmpSelectable(unit.entity);
if (cmpSelectable)
cmpSelectable->SetVisibility(unit.visibility != ICmpRangeManager::VIS_HIDDEN);
cmpSelectable->SetVisibility(unit.visibility != LosVisibility::HIDDEN);
unit.visibilityDirty = false;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,13 +22,13 @@
#include "simulation2/system/InterfaceScripted.h"
namespace {
std::string VisibilityToString(ICmpRangeManager::ELosVisibility visibility)
std::string VisibilityToString(LosVisibility visibility)
{
switch (visibility)
{
case ICmpRangeManager::VIS_HIDDEN: return "hidden";
case ICmpRangeManager::VIS_FOGGED: return "fogged";
case ICmpRangeManager::VIS_VISIBLE: return "visible";
case LosVisibility::HIDDEN: return "hidden";
case LosVisibility::FOGGED: return "fogged";
case LosVisibility::VISIBLE: return "visible";
default: return "error"; // should never happen
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,6 +22,7 @@
#include "maths/FixedVector2D.h"
#include "simulation2/system/Interface.h"
#include "simulation2/helpers/Grid.h"
#include "simulation2/helpers/Position.h"
#include "simulation2/helpers/Player.h"
@ -29,6 +30,27 @@
class FastSpatialSubdivision;
/**
* Since GetVisibility queries are run by the range manager
* other code using these must include ICmpRangeManager.h anyways,
* so define this enum here (Ideally, it'd be in its own header file,
* but adding header file does incur its own compilation time increase).
*/
enum class LosVisibility : u8
{
HIDDEN = 0,
FOGGED = 1,
VISIBLE = 2
};
enum class LosState : u8
{
UNEXPLORED = 0,
EXPLORED = 1,
VISIBLE = 2,
MASK = 3
};
/**
* Provides efficient range-based queries of the game world,
* and also LOS-based effects (fog of war).
@ -221,22 +243,6 @@ public:
*/
virtual void SetEntityFlag(entity_id_t ent, const std::string& identifier, bool value) = 0;
// LOS interface:
enum ELosState
{
LOS_UNEXPLORED = 0,
LOS_EXPLORED = 1,
LOS_VISIBLE = 2,
LOS_MASK = 3
};
enum ELosVisibility
{
VIS_HIDDEN = 0,
VIS_FOGGED = 1,
VIS_VISIBLE = 2
};
/**
* Object providing efficient abstracted access to the LOS state.
@ -250,8 +256,8 @@ public:
friend class CCmpRangeManager;
friend class TestLOSTexture;
CLosQuerier(u32 playerMask, const std::vector<u32>& data, ssize_t verticesPerSide) :
m_Data(&data[0]), m_PlayerMask(playerMask), m_VerticesPerSide(verticesPerSide)
CLosQuerier(u32 playerMask, const Grid<u32>& data, ssize_t verticesPerSide) :
m_Data(data), m_PlayerMask(playerMask), m_VerticesPerSide(verticesPerSide)
{
}
@ -267,7 +273,7 @@ public:
return false;
// Check high bit of each bit-pair
if ((m_Data[j*m_VerticesPerSide + i] & m_PlayerMask) & 0xAAAAAAAAu)
if ((m_Data.get(i, j) & m_PlayerMask) & 0xAAAAAAAAu)
return true;
else
return false;
@ -282,7 +288,7 @@ public:
return false;
// Check low bit of each bit-pair
if ((m_Data[j*m_VerticesPerSide + i] & m_PlayerMask) & 0x55555555u)
if ((m_Data.get(i, j) & m_PlayerMask) & 0x55555555u)
return true;
else
return false;
@ -298,7 +304,7 @@ public:
ENSURE(i >= 0 && j >= 0 && i < m_VerticesPerSide && j < m_VerticesPerSide);
#endif
// Check high bit of each bit-pair
if ((m_Data[j*m_VerticesPerSide + i] & m_PlayerMask) & 0xAAAAAAAAu)
if ((m_Data.get(i, j) & m_PlayerMask) & 0xAAAAAAAAu)
return true;
else
return false;
@ -314,7 +320,7 @@ public:
ENSURE(i >= 0 && j >= 0 && i < m_VerticesPerSide && j < m_VerticesPerSide);
#endif
// Check low bit of each bit-pair
if ((m_Data[j*m_VerticesPerSide + i] & m_PlayerMask) & 0x55555555u)
if ((m_Data.get(i, j) & m_PlayerMask) & 0x55555555u)
return true;
else
return false;
@ -322,9 +328,12 @@ public:
private:
u32 m_PlayerMask;
const u32* m_Data;
const Grid<u32>& m_Data;
ssize_t m_VerticesPerSide;
};
//////////////////////////////////////////////////////////////////
//// LOS interface below this line ////
//////////////////////////////////////////////////////////////////
/**
* Returns a CLosQuerier for checking whether vertex positions are visible to the given player
@ -339,17 +348,17 @@ public:
/**
* Returns the visibility status of the given entity, with respect to the given player.
* Returns VIS_HIDDEN if the entity doesn't exist or is not in the world.
* Returns LosVisibility::HIDDEN if the entity doesn't exist or is not in the world.
* This respects the GetLosRevealAll flag.
*/
virtual ELosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player) const = 0;
virtual ELosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const = 0;
virtual LosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player) const = 0;
virtual LosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const = 0;
/**
* Returns the visibility status of the given position, with respect to the given player.
* This respects the GetLosRevealAll flag.
*/
virtual ELosVisibility GetLosVisibilityPosition(entity_pos_t x, entity_pos_t z, player_id_t player) const = 0;
virtual LosVisibility GetLosVisibilityPosition(entity_pos_t x, entity_pos_t z, player_id_t player) const = 0;
/**
* Request the update of the visibility cache of ent at next turn.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -35,21 +35,21 @@ public:
return m_Script.Call<bool>("IsActivated");
}
virtual ICmpRangeManager::ELosVisibility GetVisibility(player_id_t player, bool isVisible, bool isExplored)
virtual LosVisibility GetVisibility(player_id_t player, bool isVisible, bool isExplored)
{
int visibility = m_Script.Call<int, player_id_t, bool, bool>("GetVisibility", player, isVisible, isExplored);
switch (visibility)
{
case ICmpRangeManager::VIS_HIDDEN:
return ICmpRangeManager::VIS_HIDDEN;
case ICmpRangeManager::VIS_FOGGED:
return ICmpRangeManager::VIS_FOGGED;
case ICmpRangeManager::VIS_VISIBLE:
return ICmpRangeManager::VIS_VISIBLE;
case static_cast<int>(LosVisibility::HIDDEN):
return LosVisibility::HIDDEN;
case static_cast<int>(LosVisibility::FOGGED):
return LosVisibility::FOGGED;
case static_cast<int>(LosVisibility::VISIBLE):
return LosVisibility::VISIBLE;
default:
LOGERROR("Received the invalid visibility value %d from the Visibility scripted component!", visibility);
return ICmpRangeManager::VIS_HIDDEN;
return LosVisibility::HIDDEN;
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -42,7 +42,7 @@ public:
*/
virtual bool IsActivated() = 0;
virtual ICmpRangeManager::ELosVisibility GetVisibility(player_id_t player, bool isVisible, bool isExplored) = 0;
virtual LosVisibility GetVisibility(player_id_t player, bool isVisible, bool isExplored) = 0;
virtual bool GetRetainInFog() = 0;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -20,12 +20,19 @@
#include <cstring>
#include "simulation2/serialization/IDeserializer.h"
#include "simulation2/serialization/ISerializer.h"
#ifdef NDEBUG
#define GRID_BOUNDS_DEBUG 0
#else
#define GRID_BOUNDS_DEBUG 1
#endif
template<typename T>
struct SerializedGridCompressed;
/**
* Basic 2D array, intended for storing tile data, plus support for lazy updates
* by ICmpObstructionManager.
@ -34,32 +41,54 @@
template<typename T>
class Grid
{
friend struct SerializedGridCompressed<T>;
protected:
// Tag-dispatching internal utilities for convenience.
struct default_type{};
struct is_pod { operator default_type() { return default_type{}; }};
struct is_container { operator default_type() { return default_type{}; }};
// helper to detect value_type
template <typename U, typename = int> struct has_value_type : std::false_type { };
template <typename U> struct has_value_type <U, decltype(std::declval<typename U::value_type>(), 0)> : std::true_type { };
template <typename U, typename A, typename B> using if_ = typename std::conditional<U::value, A, B>::type;
template<typename U>
using dispatch = if_< std::is_pod<U>, is_pod,
if_<has_value_type<U>, is_container,
default_type>>;
public:
Grid() : m_W(0), m_H(0), m_Data(NULL), m_DirtyID(0)
Grid() : m_W(0), m_H(0), m_Data(NULL)
{
}
Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL), m_DirtyID(0)
Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL)
{
if (m_W || m_H)
m_Data = new T[m_W * m_H];
reset();
resize(w, h);
}
Grid(const Grid& g) : m_W(0), m_H(0), m_Data(NULL), m_DirtyID(0)
Grid(const Grid& g) : m_W(0), m_H(0), m_Data(NULL)
{
*this = g;
}
using value_type = T;
public:
// Ensure that o and this are the same size before calling.
void copy_data(T* o, default_type) { std::copy(o, o + m_H*m_W, &m_Data[0]); }
void copy_data(T* o, is_pod) { memcpy(m_Data, o, m_W*m_H*sizeof(T)); }
Grid& operator=(const Grid& g)
{
if (this == &g)
return *this;
m_DirtyID = g.m_DirtyID;
if (m_W == g.m_W && m_H == g.m_H)
{
memcpy(m_Data, g.m_Data, m_W*m_H*sizeof(T));
copy_data(g.m_Data, dispatch<T>{});
return *this;
}
@ -69,7 +98,7 @@ public:
if (g.m_Data)
{
m_Data = new T[m_W * m_H];
memcpy(m_Data, g.m_Data, m_W*m_H*sizeof(T));
copy_data(g.m_Data, dispatch<T>{});
}
else
m_Data = NULL;
@ -78,7 +107,6 @@ public:
void swap(Grid& g)
{
std::swap(m_DirtyID, g.m_DirtyID);
std::swap(m_Data, g.m_Data);
std::swap(m_H, g.m_H);
std::swap(m_W, g.m_W);
@ -89,24 +117,38 @@ public:
delete[] m_Data;
}
// Ensure that o and this are the same size before calling.
bool compare_data(T* o, default_type) const { return std::equal(&m_Data[0], &m_Data[m_W*m_H], o); }
bool compare_data(T* o, is_pod) const { return memcmp(m_Data, o, m_W*m_H*sizeof(T)) == 0; }
bool operator==(const Grid& g) const
{
if (!compare_sizes(&g) || m_DirtyID != g.m_DirtyID)
if (!compare_sizes(&g))
return false;
return memcmp(m_Data, g.m_Data, m_W*m_H*sizeof(T)) == 0;
return compare_data(g.m_Data, dispatch<T>{});
}
bool operator!=(const Grid& g) const { return !(*this==g); }
bool blank() const
{
return m_W == 0 && m_H == 0;
}
bool any_set_in_square(int i0, int j0, int i1, int j1) const
u16 width() const { return m_W; };
u16 height() const { return m_H; };
bool _any_set_in_square(int, int, int, int, default_type) const
{
#if GRID_BOUNDS_DEBUG
static_assert(!std::is_same<T, T>::value, "Not implemented.");
return false; // Fix warnings.
}
bool _any_set_in_square(int i0, int j0, int i1, int j1, is_pod) const
{
#if GRID_BOUNDS_DEBUG
ENSURE(i0 >= 0 && j0 >= 0 && i1 <= m_W && j1 <= m_H);
#endif
#endif
for (int j = j0; j < j1; ++j)
{
int sum = 0;
@ -118,10 +160,30 @@ public:
return false;
}
bool any_set_in_square(int i0, int j0, int i1, int j1) const
{
return _any_set_in_square(i0, j0, i1, j1, dispatch<T>{});
}
void reset_data(default_type) { std::fill(&m_Data[0], &m_Data[m_H*m_W], T{}); }
void reset_data(is_pod) { memset(m_Data, 0, m_W*m_H*sizeof(T)); }
void reset()
{
if (m_Data)
memset(m_Data, 0, m_W*m_H*sizeof(T));
reset_data(dispatch<T>{});
}
void resize(u16 w, u16 h)
{
if (m_Data)
delete[] m_Data;
m_W = w;
m_H = h;
if (m_W || m_H)
m_Data = new T[m_W * m_H];
ENSURE(m_Data);
reset();
}
// Add two grids of the same size
@ -154,6 +216,20 @@ public:
m_Data[j*m_W + i] = value;
}
T& operator[](std::pair<u16, u16> coords) { return get(coords.first, coords.second); }
T& get(std::pair<u16, u16> coords) { return get(coords.first, coords.second); }
T& operator[](std::pair<u16, u16> coords) const { return get(coords.first, coords.second); }
T& get(std::pair<u16, u16> coords) const { return get(coords.first, coords.second); }
T& get(int i, int j)
{
#if GRID_BOUNDS_DEBUG
ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H);
#endif
return m_Data[j*m_W + i];
}
T& get(int i, int j) const
{
#if GRID_BOUNDS_DEBUG
@ -170,10 +246,62 @@ public:
u16 m_W, m_H;
T* m_Data;
size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated
};
/**
* Serialize a grid, applying a simple RLE compression that is assumed efficient.
*/
template<typename ELEM>
struct SerializedGridCompressed
{
template<typename T>
void operator()(ISerializer& serialize, const char* name, Grid<T>& value)
{
size_t len = value.m_H * value.m_W;
serialize.NumberU16_Unbounded("width", value.m_W);
serialize.NumberU16_Unbounded("height", value.m_H);
if (len == 0)
return;
u32 count = 1;
T prevVal = value.m_Data[0];
for (size_t i = 1; i < len; ++i)
{
if (prevVal == value.m_Data[i])
{
count++;
continue;
}
serialize.NumberU32_Unbounded("#", count);
ELEM()(serialize, name, prevVal);
count = 1;
prevVal = value.m_Data[i];
}
serialize.NumberU32_Unbounded("#", count);
ELEM()(serialize, name, prevVal);
}
template<typename T>
void operator()(IDeserializer& deserialize, const char* name, Grid<T>& value)
{
u16 w, h;
deserialize.NumberU16_Unbounded("width", w);
deserialize.NumberU16_Unbounded("height", h);
u32 len = h * w;
value.resize(w, h);
for (size_t i = 0; i < len;)
{
u32 count;
deserialize.NumberU32_Unbounded("#", count);
T el;
ELEM()(deserialize, name, el);
std::fill(&value.m_Data[i], &value.m_Data[i+count], el);
i += count;
}
}
};
/**
* Similar to Grid, except optimised for sparse usage (the grid is subdivided into
* buckets whose contents are only initialised on demand, to save on memset cost).

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -79,7 +79,7 @@ entity_id_t EntitySelection::PickEntityAtPoint(CSimulation2& simulation, const C
continue;
// Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld)
if (cmpRangeManager->GetLosVisibility(handle, player) == ICmpRangeManager::VIS_HIDDEN)
if (cmpRangeManager->GetLosVisibility(handle, player) == LosVisibility::HIDDEN)
continue;
return handle.GetId();
@ -126,7 +126,7 @@ bool CheckEntityInRect(CEntityHandle handle, const CCamera& camera, int sx0, int
static bool CheckEntityVisibleAndInRect(CEntityHandle handle, CmpPtr<ICmpRangeManager> cmpRangeManager, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, player_id_t player, bool allowEditorSelectables)
{
// Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld)
if (cmpRangeManager->GetLosVisibility(handle, player) == ICmpRangeManager::VIS_HIDDEN)
if (cmpRangeManager->GetLosVisibility(handle, player) == LosVisibility::HIDDEN)
return false;
return CheckEntityInRect(handle, camera, sx0, sy0, sx1, sy1, allowEditorSelectables);
@ -224,7 +224,7 @@ std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simu
// Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld)
// In this case, the checking is done to avoid selecting garrisoned units
if (cmpRangeManager->GetLosVisibility(handle, owner) == ICmpRangeManager::VIS_HIDDEN)
if (cmpRangeManager->GetLosVisibility(handle, owner) == LosVisibility::HIDDEN)
continue;
// Ignore entities not owned by 'owner'

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -30,6 +30,24 @@
#include <unordered_map>
#include <utility>
template<typename ELEM>
struct SerializeArray
{
template<typename T, size_t N>
void operator()(ISerializer& serialize, const char* name, std::array<T, N>& value)
{
for (size_t i = 0; i < N; ++i)
ELEM()(serialize, name, value[i]);
}
template<typename T, size_t N>
void operator()(IDeserializer& deserialize, const char* name, std::array<T, N>& value)
{
for (size_t i = 0; i < N; ++i)
ELEM()(deserialize, name, value[i]);
}
};
template<typename ELEM>
struct SerializeVector
{
@ -53,54 +71,7 @@ struct SerializeVector
{
T el;
ELEM()(deserialize, name, el);
value.push_back(el);
}
}
};
template<typename ELEM>
struct SerializeRepetitiveVector
{
template<typename T>
void operator()(ISerializer& serialize, const char* name, std::vector<T>& value)
{
size_t len = value.size();
serialize.NumberU32_Unbounded("length", (u32)len);
if (len == 0)
return;
u32 count = 1;
T prevVal = value[0];
for (size_t i = 1; i < len; ++i)
{
if (prevVal == value[i])
{
count++;
continue;
}
serialize.NumberU32_Unbounded("#", count);
ELEM()(serialize, name, prevVal);
count = 1;
prevVal = value[i];
}
serialize.NumberU32_Unbounded("#", count);
ELEM()(serialize, name, prevVal);
}
template<typename T>
void operator()(IDeserializer& deserialize, const char* name, std::vector<T>& value)
{
value.clear();
u32 len;
deserialize.NumberU32_Unbounded("length", len);
value.reserve(len); // TODO: watch out for out-of-memory
for (size_t i = 0; i < len;)
{
u32 count;
deserialize.NumberU32_Unbounded("#", count);
T el;
ELEM()(deserialize, name, el);
i += count;
value.insert(value.end(), count, el);
value.emplace_back(el);
}
}
};

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,6 +18,7 @@
#include "lib/self_test.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/helpers/Grid.h"
#include "simulation2/serialization/DebugSerializer.h"
#include "simulation2/serialization/SerializeTemplates.h"
@ -28,6 +29,19 @@
class TestSerializeTemplates : public CxxTest::TestSuite
{
public:
void test_Debug_array()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
std::stringstream stream;
CDebugSerializer serialize(script, stream);
std::array<u32, 6> value = {
3, 0, 1, 4, 1, 5
};
SerializeArray<SerializeU32_Unbounded>()(serialize, "E", value);
TS_ASSERT_STR_EQUALS(stream.str(), "E: 3\nE: 0\nE: 1\nE: 4\nE: 1\nE: 5\n");
}
void test_Debug_vector()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
@ -53,4 +67,23 @@ public:
SerializeSet<SerializeU32_Unbounded>()(serialize, "E", value);
TS_ASSERT_STR_EQUALS(stream.str(), "size: 5\nE: 0\nE: 1\nE: 3\nE: 4\nE: 5\n");
}
void test_Debug_grid()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
std::stringstream stream;
CDebugSerializer serialize(script, stream);
Grid<u16> value;
value.resize(3,2);
// Checkerboard pattern.
for (u8 j = 0; j < value.height(); ++j)
for (u8 i = 0; i < value.width(); ++i)
value.set(i, j, ((i % 2) + (j % 2)) % 2);
SerializedGridCompressed<SerializeU16_Unbounded>()(serialize, "E", value);
TS_ASSERT_STR_EQUALS(stream.str(), "width: 3\nheight: 2\n"
"#: 1\nE: 0\n#: 1\nE: 1\n#: 1\nE: 0\n"
"#: 1\nE: 1\n#: 1\nE: 0\n#: 1\nE: 1\n");
}
};