// BiomeCache.cpp // Implements the cBiomeCache class representing a biome source that caches data from the underlying biome source #include "Globals.h" #include "BiomeCache.h" #include "Timer.h" static int GetNumCores(void) { // Get number of cores by querying the system process affinity mask DWORD Affinity, ProcAffinity; GetProcessAffinityMask(GetCurrentProcess(), &ProcAffinity, &Affinity); int NumCores = 0; while (Affinity > 0) { if ((Affinity & 1) == 1) { NumCores++; } Affinity >>= 1; } // while (Affinity > 0) return NumCores; } cBiomeCache::cBiomeCache(void) : m_Source(NULL), m_BaseX(-100000), m_BaseZ(-100000), m_Available(NULL), m_IsTerminatingThreads(false) { int NumThreads = GetNumCores(); NumThreads--; // One core should be left for the system to run on ;) for (int i = NumThreads; i > 0; i--) { cThread * Thread = new cThread(*this); m_Threads.push_back(Thread); Thread->Start(); } } cBiomeCache::~cBiomeCache() { m_IsTerminatingThreads = true; for (cThreads::iterator itr = m_Threads.begin(), end = m_Threads.end(); itr != end; ++itr) { m_evtQueued.Set(); } for (cThreads::iterator itr = m_Threads.begin(), end = m_Threads.end(); itr != end; ++itr) { delete *itr; } m_Threads.clear(); SetSource(NULL); } cBiomeSource::eAvailability cBiomeCache::GetBiome(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_Biomes) { if (m_Source == NULL) { return baNever; } // Look up using the cache: int x = a_ChunkX - m_BaseX; int z = a_ChunkZ - m_BaseZ; if ((x < 0) || (x >= m_Width) || (z < 0) || (z >= m_Height)) { // Outside the cached region return baNever; } cCSLock Lock(m_CS); cItem * Item = m_Available[x + m_Width * z]; if (Item == NULL) { // Item hasn't been processed yet return baLater; } if (Item->m_IsValid) { memcpy(a_Biomes, Item->m_Biomes, sizeof(a_Biomes)); return baNow; } // Item has been processed, but the underlying source refused to give the data to us return baNever; } void cBiomeCache::HintViewArea(int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ) { cTimer Timer("Cache: HintViewArea"); if ( (a_MinChunkX == m_BaseX) && (a_MaxChunkX == m_BaseX + m_Width - 1) && (a_MinChunkZ == m_BaseZ) && (a_MaxChunkZ == m_BaseZ + m_Height - 1) ) { // The same set of parameters, bail out return; } if (m_Source != NULL) { m_Source->HintViewArea(a_MinChunkX, a_MaxChunkX, a_MinChunkZ, a_MaxChunkZ); } int NewWidth = a_MaxChunkX - a_MinChunkX + 1; int NewHeight = a_MaxChunkZ - a_MinChunkZ + 1; // Make a new empty cache table: pItem * NewAvailable = new pItem[NewWidth * NewHeight]; for (int i = NewWidth * NewHeight - 1; i >= 0; --i) { NewAvailable[i] = NULL; } // Move the common contents of the old table into the new table: cCSLock Lock(m_CS); for (int z = 0; z < NewHeight; z++) { int OldZ = z + a_MinChunkZ - m_BaseZ; if ((OldZ < 0) || (OldZ >= m_Height)) { continue; } for (int x = 0; x < NewWidth; x++) { int OldX = x + a_MinChunkX - m_BaseX; if ((OldX < 0) || (OldX >= m_Width)) { continue; } NewAvailable[x + NewWidth * z] = m_Available[OldX + m_Width * OldZ]; m_Available[OldX + m_Width * OldZ] = NULL; } // for x } // for z // All items that aren't common go into the pool: for (int idx = 0, z = 0; z < m_Height; z++) { for (int x = 0; x < m_Width; ++x, ++idx) { if (m_Available[idx] != NULL) { m_Pool.push_back(m_Available[idx]); m_Available[idx] = NULL; } } } // Replace the cache table: delete m_Available; m_Available = NewAvailable; m_Width = NewWidth; m_Height = NewHeight; m_BaseX = a_MinChunkX; m_BaseZ = a_MinChunkZ; // Remove all items outside the coords: FilterOutItems(m_Queue, a_MinChunkX, a_MaxChunkX, a_MinChunkZ, a_MaxChunkZ); // Queue all items from inside the coords into m_Queue: for (int z = 0; z < NewHeight; z++) { for (int x = 0; x < NewWidth; x++) { if (m_Available[x + m_Width * z] != NULL) { // Already calculated, skip continue; } if (m_Pool.empty()) { m_Pool.push_back(new cItem(x + a_MinChunkX, z + a_MinChunkZ)); } ASSERT(!m_Pool.empty()); m_Pool.back()->m_ChunkX = x + a_MinChunkX; m_Pool.back()->m_ChunkZ = z + a_MinChunkZ; m_Queue.push_back(m_Pool.back()); m_Pool.pop_back(); m_evtQueued.Set(); } // for x } // for z } void cBiomeCache::SetSource(cBiomeSource * a_Source) { // TODO: Stop all threads, so that they don't use the source anymore! delete m_Source; m_Source = a_Source; // Invalidate cache contents: cCSLock Lock(m_CS); m_BaseX = -10000; m_BaseZ = -10000; m_Pool.splice(m_Pool.end(), m_Queue); } void cBiomeCache::FilterOutItems(cItems & a_Items, int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ) { for (cItems::iterator itr = a_Items.begin(); itr != a_Items.end();) { if ( ((*itr)->m_ChunkX < a_MinChunkX) || ((*itr)->m_ChunkX > a_MaxChunkX) || ((*itr)->m_ChunkX < a_MinChunkX) || ((*itr)->m_ChunkX > a_MaxChunkX) ) { m_Pool.push_back(*itr); itr = a_Items.erase(itr); } else { ++itr; } } } void cBiomeCache::thrProcessQueueItem(void) { if (m_Source == NULL) { return; } cItem * Item = NULL; { cCSLock Lock(m_CS); if (m_Queue.empty()) { cCSUnlock Unlock(Lock); m_evtQueued.Wait(); } if (m_IsTerminatingThreads || m_Queue.empty()) { // We've been woken up only to die / spurious wakeup return; } Item = m_Queue.back(); m_Queue.pop_back(); } // Process the item: Item->m_IsValid = (m_Source->GetBiome(Item->m_ChunkX, Item->m_ChunkZ, Item->m_Biomes) == baNow); // Store result: cCSLock Lock(m_CS); int x = Item->m_ChunkX - m_BaseX; int z = Item->m_ChunkZ - m_BaseZ; if ((x < 0) || (x >= m_Width) || (z < 0) || (z >= m_Height)) { // The cache rectangle has changed under our fingers, drop this chunk return; } m_Available[x + m_Width * z] = Item; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cBiomeCache::cItem: cBiomeCache::cItem::cItem(int a_ChunkX, int a_ChunkZ) : m_ChunkX(a_ChunkX), m_ChunkZ(a_ChunkZ) { } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cBiomeCache::cThread: cBiomeCache::cThread::cThread(cBiomeCache & a_Parent) : super("Biome cache thread"), m_Parent(a_Parent) { } void cBiomeCache::cThread::Execute(void) { while (!m_ShouldTerminate && !m_Parent.m_IsTerminatingThreads) { m_Parent.thrProcessQueueItem(); } }