#include "gwpaging.h" #include #include namespace geoworld { PagingStore::PageMutexProvider::PageMutexProvider( const unsigned int nCellsX, const unsigned int nCellsY ) : _nW(nCellsX), _nH(nCellsY), _szLen (nCellsX * nCellsY) { _ppShMutexes = new boost::shared_mutex * [_szLen]; for (size_t c = 0; c < _szLen; ++c) _ppShMutexes[c] = new boost::shared_mutex(); } PagingStore::PageMutexProvider::~PageMutexProvider() { for (size_t c = 0; c < _szLen; ++c) delete _ppShMutexes[c]; delete [] _ppShMutexes; } size_t PagingStore::PageMutexProvider::idx( const long nPageX, const long nPageY ) const { return mars::WRAP(nPageY, _nH) * _nW + mars::WRAP(nPageX, _nW); } PagingStore::PageMutexProvider::NeighborhoodMutex PagingStore::PageMutexProvider::acquireNeighborhood( const long nPageX, const long nPageY ) { boost::shared_mutex * ppNeigh[NeighborhoodMutex::TotalSpots]; ppNeigh[NeighborhoodMutex::N] = _ppShMutexes[idx(nPageX, nPageY - 1)]; ppNeigh[NeighborhoodMutex::W] = _ppShMutexes[idx(nPageX - 1, nPageY)]; ppNeigh[NeighborhoodMutex::C] = _ppShMutexes[idx(nPageX, nPageY)]; ppNeigh[NeighborhoodMutex::E] = _ppShMutexes[idx(nPageX + 1, nPageY)]; ppNeigh[NeighborhoodMutex::S] = _ppShMutexes[idx(nPageX, nPageY + 1)]; return NeighborhoodMutex(ppNeigh); } GeoWorldPage * PagingStore::requestPage( const unsigned int nPageX, const unsigned int nPageY ) { GeoWorldPage * pPage; assert(nPageX < getNumPagesWide() && nPageY < getNumPagesHigh()); { GW_SCOPED_LOCK(mtxMaster); LOG(mars::Log::Debug) << "Loading DEM for page " << nPageX << "x" << nPageY; _pFile->tilemap.seekg(nPageX, nPageY, std::ios::beg); _pFile->tilemap >> *_pUtilityBlock; pPage = new GeoWorldPage(*_pUtilityBlock, nPageX, nPageY, _pFile->getWorldDepth(), _pFile->geohost.count); } GeoWorldPage::Interface page(pPage->acquireInterface()); InstanceFile * pInst; { PageMutexProvider::NeighborhoodMutex neighborhood = _pMutexProvider->acquireNeighborhood(nPageX, nPageY); // Will block if any neighbor pages are busy rendering LOG(mars::Log::Debug) << "Acquired read lock on " << nPageX << "x" << nPageY << ", checking for an instance file..."; pInst = loadInstanceFile(nPageX, nPageY); if (pInst == NULL) { LOG(mars::Log::Debug) << "Instance file non-existent, preparing to generate page " << nPageX << "x" << nPageY; neighborhood.genLock(); LOG(mars::Log::Debug) << "Write-lock acquired for page " << nPageX << "x" << nPageY << " and for neighboring tiles"; // Check again to see if this was actually a bogus redundant page request and the first thread already did the generating pInst = loadInstanceFile(nPageX, nPageY); if (pInst == NULL) { pInst = new InstanceFile(_pFile->getPhysicalTileDim(), _pFile->getWorldDepth(), _pFile->geohost.count); mars::ptr< FileSource::MineralHostMap::LayerMap::Block > pLayerBlock = _pFile->geohost.createLayerBlock(); mars::ptr< FileSource::MineralHostMap::TopDEM::Block > pHeightBlock = _pFile->geohost.createTopDEMBlock(); enum Neighbor { N = 0, W = 1, E = 2, S = 3 }; InstanceFile * pInstNeighbors[4]; // Use virtual tile dim here (a power of 2) because MPDG will automatically add one unit to make non-even dimensions as is required. GeoStratumMPDG * pLayerMPDG = GeoStratumMPDG::createInstance(_pFile->getVirtualTileDim(), _pFile->getVirtualTileDim(), mars::Normal); GeoHeightMPDG * pHeightMPDG = GeoHeightMPDG::createInstance(_pFile->getVirtualTileDim(), _pFile->getVirtualTileDim(), mars::Normal); // TODO: Cache loaded instance files pInstNeighbors[W] = loadInstanceFile(mars::WRAP(static_cast< signed long > (nPageX) - 1, getNumPagesWide()), nPageY); pInstNeighbors[E] = loadInstanceFile(mars::WRAP(static_cast< signed long > (nPageX) + 1, getNumPagesWide()), nPageY); pInstNeighbors[N] = loadInstanceFile(nPageX, mars::WRAP(static_cast< signed long > (nPageY) - 1, getNumPagesHigh())); pInstNeighbors[S] = loadInstanceFile(nPageX, mars::WRAP(static_cast< signed long > (nPageY) + 1, getNumPagesHigh())); { GW_SCOPED_LOCK(mtxMaster); _pFile->geohost.top.seekg(nPageX, nPageY, std::ios::beg); _pFile->geohost.top >> *pHeightBlock; } pHeightMPDG->clear(); pHeightMPDG->seedFrom(*pHeightBlock, 0, 0, _pFile->geohost.getLOD()); int nStitch = 0; if (pInstNeighbors[W] != NULL) nStitch |= mars::StitchLeft; if (pInstNeighbors[E] != NULL) nStitch |= mars::StitchRight; if (pInstNeighbors[N] != NULL) nStitch |= mars::StitchTop; if (pInstNeighbors[S] != NULL) nStitch |= mars::StitchBottom; LOG(mars::Log::Debug) << "Stitch Flags: " << nStitch; pHeightMPDG->stitchAll( nStitch, pInstNeighbors[W] == NULL ? NULL : & pInstNeighbors[W]->getStratum().getBase(), pInstNeighbors[N] == NULL ? NULL : & pInstNeighbors[N]->getStratum().getBase(), pInstNeighbors[E] == NULL ? NULL : & pInstNeighbors[E]->getStratum().getBase(), pInstNeighbors[S] == NULL ? NULL : & pInstNeighbors[S]->getStratum().getBase() ); pHeightMPDG->run(mars::RANDf(1.0f), nStitch); // TODO: Export coarseness constant pInst->top() << *pHeightMPDG; for (unsigned short c = 0; c < _pFile->geohost.count; ++c) { FileSource::MineralHostMap::LayerMap & layer = _pFile->geohost.layer(c); { GW_SCOPED_LOCK(mtxMaster); layer.seekg(nPageX, nPageY, std::ios::beg); layer >> *pLayerBlock; } assert(pLayerBlock->range().minimum.altitude >= 0); pLayerMPDG->clear(); pLayerMPDG->seedFrom(*pLayerBlock, 0, 0, _pFile->geohost.getLOD()); layer.putDefs(*pLayerMPDG); nStitch = 0; if (pInstNeighbors[W] != NULL) nStitch |= mars::StitchLeft; if (pInstNeighbors[E] != NULL) nStitch |= mars::StitchRight; if (pInstNeighbors[N] != NULL) nStitch |= mars::StitchTop; if (pInstNeighbors[S] != NULL) nStitch |= mars::StitchBottom; LOG(mars::Log::Debug) << "Stitch Flags: " << nStitch; pLayerMPDG->stitchAll( nStitch, pInstNeighbors[W] == NULL ? NULL : & pInstNeighbors[W]->layer(c), pInstNeighbors[N] == NULL ? NULL : & pInstNeighbors[N]->layer(c), pInstNeighbors[E] == NULL ? NULL : & pInstNeighbors[E]->layer(c), pInstNeighbors[S] == NULL ? NULL : & pInstNeighbors[S]->layer(c) ); pLayerMPDG->run(mars::RANDf(1.0f), nStitch); // TODO: Export coarseness constant pInst->layer(c) << *pLayerMPDG; assert(pInst->layer(c).elementCount() > 0); } pInst->getStratum().updateIndexMap(); delete pHeightMPDG; delete pLayerMPDG; delete pInstNeighbors[N]; delete pInstNeighbors[S]; delete pInstNeighbors[W]; delete pInstNeighbors[E]; FileSource::MineralMap::TileResult * pMinerals; { GW_SCOPED_LOCK(mtxMaster); pMinerals = new FileSource::MineralMap::TileResult(_pFile->minerals(nPageX, nPageY)); } for (FileSource::MineralMap::TileResult::const_iterator i = pMinerals->begin(); i != pMinerals->end(); ++i ) { // TODO: LookupTree API is too rigid page.minerals.add(new MineralPtr((*i).first), OctTreeMineralDeposits::MyRadialRegion((*i).second)); } delete pMinerals; LOG(mars::Log::Debug) << "Writing generated instance file to disk for page " << nPageX << "x" << nPageY; InstanceFile::write(makeFilename(nPageX, nPageY), pInst); } else LOG(mars::Log::Debug) << "Bogus redundant request, page was already generated, using its instance file instead"; } } LOGD << "Out of critical section for " << nPageX << "x" << nPageY; pInst->getStratum() >> page.stratum; delete pInst; LOGD << "Page for " << nPageX << "x" << nPageY << " loaded"; return pPage; } void PagingStore::releasePage( GeoWorldPage * pPage ) { delete pPage; } PagingStore::~PagingStore() { delete _pMutexProvider; delete _pUtilityBlock; delete _pFile; } PagingStore::PagingStore( const std::string & sFileName, const std::string & sStoreDirectory, const std::string & sFilePrefix /*= "gwpage-"*/, const std::string & sFileSuffix /*= ".gwp"*/ ) : _pFile(NULL), _pUtilityBlock(NULL), _sFilePrefix(sFilePrefix), _sFileSuffix(sFileSuffix), _sStoreDirectory(sStoreDirectory) { _pFile = FileSource::open(sFileName); _pFile->load(); _pUtilityBlock = _pFile->tilemap.createBlock(); _pMutexProvider = new PageMutexProvider(_pFile->getTilesWide(), _pFile->getTilesHigh()); } std::string PagingStore::makeFilename( const unsigned int nPageX, const unsigned int nPageY ) const { std::stringstream ss; ss << _sStoreDirectory << '/' << _sFilePrefix << std::setw(8) << std::setfill('0') << std::hex << calculatePageID(nPageX, nPageY) << _sFileSuffix << std::ends; return ss.str(); } unsigned long PagingStore::calculatePageID( const unsigned int nPageX, const unsigned int nPageY ) { return ((nPageX & 0xFFFF) << 16) | (nPageY & 0xFFFF); } InstanceFile * PagingStore::loadInstanceFile( const unsigned int nPageX, const unsigned int nPageY ) const { try { return InstanceFile::load(makeFilename(nPageX, nPageY)); } catch (InstanceFile::Ex &) {} return NULL; } void PagingStore::logBorders( const GeoHeightMap::View & base, const unsigned int nPageX, const unsigned int nPageY ) { enum Neighbor { N = 0, W = 1, E = 2, S = 3 }; const GeoHeightMap::View * pSlices[4]; pSlices[N] = base.createView(0, 0, base.width - 1, 0); pSlices[E] = base.createView(base.width - 1, 0, base.width - 1, base.height - 1); pSlices[S] = base.createView(0, base.height - 1, base.width - 1, base.height - 1); pSlices[W] = base.createView(0, 0, 0, base.height - 1); std::vector< std::vector< GeoHeightMap::Precision > > vecSlices[4]; *pSlices[N] >> vecSlices[N]; *pSlices[E] >> vecSlices[E]; *pSlices[S] >> vecSlices[S]; *pSlices[W] >> vecSlices[W]; LOGD << "\tPrinting slices for page (" << nPageX << ',' << nPageY << ")\n" << "\t\tN: " << pSlices[N]->bbox() << " : " << vecSlices[N] << '\n' << "\t\tE: " << pSlices[E]->bbox() << " : " << vecSlices[E] << '\n' << "\t\tS: " << pSlices[S]->bbox() << " : " << vecSlices[S] << '\n' << "\t\tW: " << pSlices[W]->bbox() << " : " << vecSlices[W]; base.releaseView(pSlices[N]); base.releaseView(pSlices[E]); base.releaseView(pSlices[S]); base.releaseView(pSlices[W]); } bool InstanceFile::InfoHeader::validate() const { return strncmp(IDENT, "GWIF", sizeof(IDENT)) == 0 && endian == 1 && version == 1 && dim > 0 && depth > 0; } InstanceFile::InfoHeader::InfoHeader() : endian(1), version(1), stratumcount(0), dim(0), depth(0) { memcpy(IDENT, "GWIF", sizeof(IDENT)); } void InstanceFile::write( const std::string & sFileName, const InstanceFile * pInstFile ) { std::fstream * pOut = open (sFileName, std::ios::out); if (!*pOut) throw Ex("Failed to create file"); *pInstFile >> *pOut; delete pOut; } InstanceFile * InstanceFile::load( const std::string & sFileName ) { mars::ptr< std::fstream > pIn = open (sFileName, std::ios::in); InstanceFile * pInst = NULL; if (!*pIn) { LOG(mars::Log::Debug) << "Failure loading InstanceFile: " << sFileName; return NULL; } pInst = new InstanceFile(); *pInst << *pIn; return pInst; } std::fstream * InstanceFile::open( const std::string & sFileName, const std::ios::openmode & nMode ) { const std::ios::openmode nEffectiveMode = nMode | std::ios::binary | ((nMode & std::ios::out) != 0 ? std::ios::trunc | std::ios::ate : static_cast(0)); return new std::fstream(sFileName, nEffectiveMode); } std::ostream & InstanceFile::operator >> ( std::ostream & outs ) const { assert(_pStratum != NULL && _nDim > 0 && _nDepth > 0); InfoHeader header; header.stratumcount = _pStratum->size(); header.dim = _nDim; header.depth = _nDepth; outs.write(reinterpret_cast< const char * > (&header), sizeof(header)); return *_pStratum >> outs; } std::istream & InstanceFile::operator << ( std::istream & ins ) { InfoHeader header; ins.read(reinterpret_cast< char * > (&header), sizeof(header)); if (!header.validate()) throw Ex("Failed to validate file"); delete _pStratum; _nDim = header.dim; _nDepth = header.depth; _pStratum = new Stratum(header.stratumcount, header.dim, header.dim, header.depth, 0); return *_pStratum << ins; } InstanceFile::InstanceFile() : _pStratum(NULL), _nDim(0), _nDepth(0) {} InstanceFile::InstanceFile( const unsigned short nDim, const unsigned short nDepth, const unsigned short nLayerCount ) : _pStratum(new Stratum(nLayerCount, nDim, nDim, nDepth, 0)), _nDim(nDim), _nDepth(nDepth) { assert(((nDim - 2) & (nDim - 1)) == 0); // Must be a power of 2 + 1 (normal non-wrapped MPDG-compatible field) } InstanceFile::~InstanceFile() { delete _pStratum; } DEFN_STATIC_GW_MUTEX(PagingStore::PageMutexProvider::NeighborhoodMutex, ATOMIC); PagingStore::PageMutexProvider::NeighborhoodMutex::NeighborhoodMutex( boost::shared_mutex * const * ppShMutexes ) : _plckCenter(NULL) { for (unsigned int c = 0; c < TotalSpots; ++c) { _pmtxNeighbors[c] = ppShMutexes[c]; _plckNeighbors[c] = NULL; } _plckPreamble = new preamble_lock(*ppShMutexes[C]); } PagingStore::PageMutexProvider::NeighborhoodMutex::NeighborhoodMutex( NeighborhoodMutex && movable ) : _plckPreamble(movable._plckPreamble), _plckCenter(movable._plckCenter) { if (&movable != this) { for (unsigned int c = 0; c < TotalSpots; ++c) { _plckNeighbors[c] = movable._plckNeighbors[c]; _pmtxNeighbors[c] = movable._pmtxNeighbors[c]; movable._pmtxNeighbors[c] = NULL; movable._plckNeighbors[c] = NULL; } movable._plckPreamble = NULL; movable._plckCenter = NULL; } } PagingStore::PageMutexProvider::NeighborhoodMutex::~NeighborhoodMutex() { { GW_SCOPED_LOCK(ATOMIC); for (unsigned int c = 0; c < TotalSpots; ++c) delete _plckNeighbors[c]; delete _plckCenter; delete _plckPreamble; } } void PagingStore::PageMutexProvider::NeighborhoodMutex::genLock() { assert(_plckPreamble != NULL && *_plckPreamble); assert( _plckNeighbors[N] == NULL && _plckNeighbors[E] == NULL && _plckNeighbors[S] == NULL && _plckNeighbors[W] == NULL && _plckNeighbors[C] == NULL ); Spot enLockedSpot; bool bUnavailable = false; do { { GW_SCOPED_LOCK(ATOMIC); _plckPreamble->unlock(); // Release read lock on center tile { // Try-establish write-lock on center tile DetachableScopedGenerationTryLock lckCenter(*_pmtxNeighbors[C]); bUnavailable = !lckCenter; if (lckCenter) { DetachableScopedQueryTryLock lckN (*_pmtxNeighbors[N]), lckS (*_pmtxNeighbors[S]), lckW (*_pmtxNeighbors[W]), lckE (*_pmtxNeighbors[E]); bUnavailable = !(lckN && lckS && lckW && lckE); if (bUnavailable) { if (!lckN) enLockedSpot = N; if (!lckS) enLockedSpot = S; if (!lckW) enLockedSpot = W; if (!lckE) enLockedSpot = E; } else { _plckNeighbors[N] = lckN.detach(); _plckNeighbors[S] = lckS.detach(); _plckNeighbors[W] = lckW.detach(); _plckNeighbors[E] = lckE.detach(); _plckCenter = lckCenter.detach(); } } else enLockedSpot = C; if (bUnavailable) _plckPreamble->lock(); // Not supposed to block, we unlocked a previously-held shared-lock inside this same critical section } } // Occurs outside of main atomic locking operation, waits for something to finish before attempting atomic locking transaction again if (bUnavailable) { if (enLockedSpot == C) { upgraded_lock cond(*_plckPreamble); } else { qry_lock cond(*_pmtxNeighbors[enLockedSpot]); } } } while (bUnavailable); } GeoWorldPage::GeoWorldPage( const GeoHeightMap::View & hm, const unsigned int nPageX, const unsigned int nPageY, const unsigned short nDepth, const size_t nLayers ) // TODO: Check bounds for the OctTree, is this good enough? : _heightmap(hm), _stratum(nLayers, hm.width, hm.height, nDepth, 0), _minerals(std::max(hm.width, hm.height)), _nIFaceRefCount(0), _bShouldAbort(false), x(nPageX), y(nPageY) { assert(((hm.width - 2) & (hm.width - 1)) == 0 && ((hm.height - 2) & (hm.height - 1)) == 0); // Must be a power of 2 + 1 (normal non-wrapped MPDG-compatible field) } GeoWorldPage::~GeoWorldPage() { { GW_SCOPED_LOCK(atomic); assert(_nIFaceRefCount == 0); if (_nIFaceRefCount > 0) throw IFaceEx("Still open interface on GeoWorldPage instance"); } } GeoWorldPage::Interface::Interface(GeoWorldPage * pParent) : _pParent(pParent), heightmap(pParent->_heightmap), stratum(pParent->_stratum), minerals(pParent->_minerals) { _pParent->checkOutIFace(); } GeoWorldPage::Interface::Interface(GeoWorldPage::Interface && move) : _pParent(move._pParent), heightmap(move.heightmap), stratum(move.stratum), minerals(move.minerals) { move._pParent = NULL; } GeoWorldPage::Interface::~Interface() { // TODO: Stratum::~Stratum() needs to be atomic if (_pParent != NULL) _pParent->checkInIFace(); } Stratum::View * GeoWorldPage::Interface::createStratumView( const WorldUnit nLeft, const WorldUnit nTop, const int nShallow, const WorldUnit nRight, const WorldUnit nBottom, const WorldUnit nDeep ) const { { GW_SCOPED_LOCK(atomic); return _pParent->_stratum.createView(nLeft, nTop, nShallow, nRight, nBottom, nDeep); } } Stratum::View * GeoWorldPage::Interface::createStratumView( const WorldUnit nLeft, const WorldUnit nTop, const WorldUnit nRight, const WorldUnit nBottom ) const { { GW_SCOPED_LOCK(atomic); return _pParent->_stratum.createView(nLeft, nTop, nRight, nBottom); } } void GeoWorldPage::Interface::releaseStratumView( const Stratum::View * pView ) const { { GW_SCOPED_LOCK(atomic); // TODO: Verify, is it correct to delete inside critical section? _pParent->_stratum.releaseView(pView); } } GeoHeightMap::View * GeoWorldPage::Interface::createHeightMapView( const unsigned short nLeft, const unsigned short nTop, const unsigned short nRight, const unsigned short nBottom ) const { { GW_SCOPED_LOCK(atomic); return heightmap.createView(nLeft, nTop, nRight, nBottom); } } void GeoWorldPage::Interface::releaseHeightMapView( const GeoHeightMap::View * pView ) const { { GW_SCOPED_LOCK(atomic); heightmap.releaseView(pView); } } void GeoWorldPage::abort(const bool bWait /*= false*/) const { { GW_SCOPED_LOCK(atomic); _bShouldAbort = true; } if (bWait) GW_WAIT(work); } bool GeoWorldPage::isAborted() const { { GW_SCOPED_LOCK(atomic); return _bShouldAbort; } } void GeoWorldPage::checkOutIFace() const { _work.lock(); { GW_SCOPED_LOCK(atomic); ++_nIFaceRefCount; } } void GeoWorldPage::checkInIFace() const { { GW_SCOPED_LOCK(atomic); --_nIFaceRefCount; } _work.unlock(); } }