diff options
Diffstat (limited to 'source/game/StarCelestialDatabase.cpp')
-rw-r--r-- | source/game/StarCelestialDatabase.cpp | 828 |
1 files changed, 828 insertions, 0 deletions
diff --git a/source/game/StarCelestialDatabase.cpp b/source/game/StarCelestialDatabase.cpp new file mode 100644 index 0000000..718bfde --- /dev/null +++ b/source/game/StarCelestialDatabase.cpp @@ -0,0 +1,828 @@ +#include "StarCelestialDatabase.hpp" +#include "StarLexicalCast.hpp" +#include "StarCasting.hpp" +#include "StarRandom.hpp" +#include "StarCompression.hpp" +#include "StarFile.hpp" +#include "StarJsonExtra.hpp" +#include "StarDataStreamExtra.hpp" +#include "StarRoot.hpp" +#include "StarAssets.hpp" +#include "StarVersioningDatabase.hpp" +#include "StarIterator.hpp" + +namespace Star { + +CelestialDatabase::~CelestialDatabase() {} + +RectI CelestialDatabase::xyRange() const { + auto range = m_baseInformation.xyCoordRange; + return RectI(range[0], range[0], range[1], range[1]); +} + +int CelestialDatabase::planetOrbitalLevels() const { + return m_baseInformation.planetOrbitalLevels; +} + +int CelestialDatabase::satelliteOrbitalLevels() const { + return m_baseInformation.satelliteOrbitalLevels; +} + +Vec2I CelestialDatabase::chunkIndexFor(CelestialCoordinate const& coordinate) const { + return chunkIndexFor(coordinate.location().vec2()); +} + +Vec2I CelestialDatabase::chunkIndexFor(Vec2I const& systemXY) const { + return {(systemXY[0] - pmod(systemXY[0], m_baseInformation.chunkSize)) / m_baseInformation.chunkSize, + (systemXY[1] - pmod(systemXY[1], m_baseInformation.chunkSize)) / m_baseInformation.chunkSize}; +} + +List<Vec2I> CelestialDatabase::chunkIndexesFor(RectI const& region) const { + if (region.isEmpty()) + return {}; + + List<Vec2I> chunkLocations; + RectI chunkRegion(chunkIndexFor(region.min()), chunkIndexFor(region.max() - Vec2I(1, 1))); + for (int x = chunkRegion.xMin(); x <= chunkRegion.xMax(); ++x) { + for (int y = chunkRegion.yMin(); y <= chunkRegion.yMax(); ++y) + chunkLocations.append({x, y}); + } + return chunkLocations; +} + +RectI CelestialDatabase::chunkRegion(Vec2I const& chunkIndex) const { + return RectI(chunkIndex * m_baseInformation.chunkSize, (chunkIndex + Vec2I(1, 1)) * m_baseInformation.chunkSize); +} + +CelestialMasterDatabase::CelestialMasterDatabase(Maybe<String> databaseFile) { + auto assets = Root::singleton().assets(); + + auto config = assets->json("/celestial.config"); + + m_baseInformation.planetOrbitalLevels = config.getInt("planetOrbitalLevels"); + m_baseInformation.satelliteOrbitalLevels = config.getInt("satelliteOrbitalLevels"); + m_baseInformation.chunkSize = config.getInt("chunkSize"); + m_baseInformation.xyCoordRange = jsonToVec2I(config.get("xyCoordRange")); + m_baseInformation.zCoordRange = jsonToVec2I(config.get("zCoordRange")); + + m_generationInformation.systemProbability = config.getFloat("systemProbability"); + m_generationInformation.constellationProbability = config.getFloat("constellationProbability"); + m_generationInformation.constellationLineCountRange = jsonToVec2U(config.get("constellationLineCountRange")); + m_generationInformation.constellationMaxTries = config.getUInt("constellationMaxTries"); + m_generationInformation.maximumConstellationLineLength = config.getFloat("maximumConstellationLineLength"); + m_generationInformation.minimumConstellationLineLength = config.getFloat("minimumConstellationLineLength"); + m_generationInformation.minimumConstellationMagnitude = config.getFloat("minimumConstellationMagnitude"); + m_generationInformation.minimumConstellationLineCloseness = config.getFloat("minimumConstellationLineCloseness"); + + // Copy construct into a Map<String, Json> in the parsing of the weighted + // pools to make sure that each WeightedPool is predictably populated based + // on key order. + + for (auto const& systemPair : Map<String, Json>::from(config.getObject("systemTypes"))) { + SystemType systemType; + systemType.typeName = systemPair.first; + systemType.baseParameters = systemPair.second.get("baseParameters"); + systemType.variationParameters = systemPair.second.getArray("variationParameters", JsonArray()); + for (auto const& orbitRegion : systemPair.second.getArray("orbitRegions", JsonArray())) { + String regionName = orbitRegion.getString("regionName"); + Vec2I orbitRange = jsonToVec2I(orbitRegion.get("orbitRange")); + float bodyProbability = orbitRegion.getFloat("bodyProbability"); + WeightedPool<String> regionPlanetaryTypes = jsonToWeightedPool<String>(orbitRegion.get("planetaryTypes")); + WeightedPool<String> regionSatelliteTypes = jsonToWeightedPool<String>(orbitRegion.get("satelliteTypes")); + systemType.orbitRegions.append({regionName, orbitRange, bodyProbability, regionPlanetaryTypes, regionSatelliteTypes}); + } + m_generationInformation.systemTypes.add(systemPair.first, systemType); + } + + m_generationInformation.systemTypePerlin = PerlinD(config.getObject("systemTypePerlin"), staticRandomU64("SystemTypePerlin")); + m_generationInformation.systemTypeBins = config.get("systemTypeBins"); + + for (auto const& planetaryPair : Map<String, Json>::from(config.getObject("planetaryTypes"))) { + PlanetaryType planetaryType; + planetaryType.typeName = planetaryPair.first; + planetaryType.satelliteProbability = planetaryPair.second.getFloat("satelliteProbability"); + planetaryType.maxSatelliteCount = + planetaryPair.second.getUInt("maxSatelliteCount", m_baseInformation.satelliteOrbitalLevels); + planetaryType.baseParameters = planetaryPair.second.get("baseParameters"); + planetaryType.variationParameters = planetaryPair.second.getArray("variationParameters", JsonArray()); + planetaryType.orbitParameters = planetaryPair.second.getObject("orbitParameters", JsonObject()); + m_generationInformation.planetaryTypes[planetaryType.typeName] = planetaryType; + } + + for (auto const& satellitePair : Map<String, Json>::from(config.getObject("satelliteTypes"))) { + SatelliteType satelliteType; + satelliteType.typeName = satellitePair.first; + satelliteType.baseParameters = satellitePair.second.get("baseParameters"); + satelliteType.variationParameters = satellitePair.second.getArray("variationParameters", JsonArray()); + satelliteType.orbitParameters = satellitePair.second.getObject("orbitParameters", JsonObject()); + m_generationInformation.satelliteTypes[satelliteType.typeName] = satelliteType; + } + + auto namesConfig = assets->json("/celestial/names.config"); + m_generationInformation.planetarySuffixes = jsonToStringList(namesConfig.get("planetarySuffixes")); + m_generationInformation.satelliteSuffixes = jsonToStringList(namesConfig.get("satelliteSuffixes")); + + for (auto const& list : namesConfig.get("systemPrefixNames").iterateArray()) + m_generationInformation.systemPrefixNames.add(list.getFloat(0), list.getString(1)); + + for (auto const& list : namesConfig.get("systemNames").iterateArray()) + m_generationInformation.systemNames.add(list.getFloat(0), list.getString(1)); + + for (auto const& list : namesConfig.get("systemSuffixNames").iterateArray()) + m_generationInformation.systemSuffixNames.add(list.getFloat(0), list.getString(1)); + + if (databaseFile) { + m_database.setContentIdentifier("Celestial2"); + m_database.setIODevice(File::open(*databaseFile, IOMode::ReadWrite)); + m_database.open(); + if (m_database.contentIdentifier() != "Celestial2") { + Logger::error("CelestialMasterDatabase database content identifier is not 'Celestial2', moving out of the way and recreating"); + m_database.close(); + File::rename(*databaseFile, strf("%s.%s.fail", *databaseFile, Time::millisecondsSinceEpoch())); + m_database.setIODevice(File::open(*databaseFile, IOMode::ReadWrite)); + m_database.open(); + } + m_database.setAutoCommit(false); + } + + m_commitInterval = config.getFloat("commitInterval"); + m_commitTimer.restart(m_commitInterval); +} + +CelestialBaseInformation CelestialMasterDatabase::baseInformation() const { + return m_baseInformation; +} + +CelestialResponse CelestialMasterDatabase::respondToRequest(CelestialRequest const& request) { + RecursiveMutexLocker locker(m_mutex); + + if (auto chunkLocation = request.maybeLeft()) { + auto chunk = getChunk(*chunkLocation); + // System objects are sent by separate system requests. + chunk.systemObjects.clear(); + return makeLeft(move(chunk)); + } else if (auto systemLocation = request.maybeRight()) { + auto const& chunk = getChunk(chunkIndexFor(*systemLocation)); + CelestialSystemObjects systemObjects = {*systemLocation, chunk.systemObjects.get(*systemLocation)}; + return makeRight(move(systemObjects)); + } else { + return CelestialResponse(); + } +} + +void CelestialMasterDatabase::cleanupAndCommit() { + RecursiveMutexLocker locker(m_mutex); + m_chunkCache.cleanup(); + if (m_database.isOpen() && m_commitTimer.timeUp()) { + m_database.commit(); + m_commitTimer.restart(m_commitInterval); + } +} + +bool CelestialMasterDatabase::coordinateValid(CelestialCoordinate const& coordinate) { + RecursiveMutexLocker locker(m_mutex); + + if (!coordinate) + return false; + + auto const& chunk = getChunk(chunkIndexFor(coordinate)); + + auto systemObjects = chunk.systemObjects.ptr(coordinate.location()); + if (!systemObjects) + return false; + + if (coordinate.isSystem()) + return true; + + auto planet = systemObjects->ptr(coordinate.planet().orbitNumber()); + if (!planet) + return false; + + if (coordinate.isPlanetaryBody()) + return true; + + return planet->satelliteParameters.contains(coordinate.orbitNumber()); +} + +Maybe<CelestialCoordinate> CelestialMasterDatabase::findRandomWorld(unsigned tries, unsigned trySpatialRange, + function<bool(CelestialCoordinate)> filter, Maybe<uint64_t> seed) { + RecursiveMutexLocker locker(m_mutex); + RandomSource randSource; + if (seed) + randSource.init(*seed); + for (unsigned i = 0; i < tries; ++i) { + RectI range = xyRange(); + Vec2I randomLocation = Vec2I(randSource.randInt(range.xMin(), range.xMax()), randSource.randInt(range.yMin(), range.yMax())); + for (auto system : scanSystems(RectI::withCenter(randomLocation, Vec2I::filled(trySpatialRange)))) { + if (!hasChildren(system).value(false)) + continue; + + auto world = randSource.randFrom(children(system)); + // This sucks, 50% of the time will try and return satellite, not really + // balanced probability wise + if (hasChildren(world).value(false) && randSource.randb()) + world = randSource.randFrom(children(world)); + + if (!filter || filter(world)) + return world; + } + } + + return {}; +} + +Maybe<CelestialParameters> CelestialMasterDatabase::parameters(CelestialCoordinate const& coordinate) { + RecursiveMutexLocker locker(m_mutex); + + if (!coordinateValid(coordinate)) + throw CelestialException("CelestialMasterDatabase::parameters called on invalid coordinate"); + + auto const& chunk = getChunk(chunkIndexFor(coordinate)); + + if (coordinate.isSatelliteBody()) + return chunk.systemObjects.get(coordinate.location()) + .get(coordinate.parent().orbitNumber()) + .satelliteParameters.get(coordinate.orbitNumber()); + + if (coordinate.isPlanetaryBody()) + return chunk.systemObjects.get(coordinate.location()).get(coordinate.orbitNumber()).planetParameters; + + return chunk.systemParameters.get(coordinate.location()); +} + +Maybe<String> CelestialMasterDatabase::name(CelestialCoordinate const& coordinate) { + return parameters(coordinate)->name(); +} + +Maybe<bool> CelestialMasterDatabase::hasChildren(CelestialCoordinate const& coordinate) { + RecursiveMutexLocker locker(m_mutex); + + if (!coordinateValid(coordinate)) + throw CelestialException("CelestialMasterDatabase::hasChildren called on invalid coordinate"); + + auto const& systemObjects = getChunk(chunkIndexFor(coordinate)).systemObjects.get(coordinate.location()); + + if (coordinate.isSystem()) + return !systemObjects.empty(); + + if (coordinate.isPlanetaryBody()) + return !systemObjects.get(coordinate.orbitNumber()).satelliteParameters.empty(); + + return false; +} + +List<CelestialCoordinate> CelestialMasterDatabase::children(CelestialCoordinate const& coordinate) { + return childOrbits(coordinate).transformed(bind(&CelestialCoordinate::child, coordinate, _1)); +} + +List<int> CelestialMasterDatabase::childOrbits(CelestialCoordinate const& coordinate) { + RecursiveMutexLocker locker(m_mutex); + + if (!coordinateValid(coordinate)) + throw CelestialException("CelestialMasterDatabase::childOrbits called on invalid coordinate"); + + auto const& systemObjects = getChunk(chunkIndexFor(coordinate)).systemObjects.get(coordinate.location()); + + if (coordinate.isSystem()) + return systemObjects.keys(); + + if (coordinate.isPlanetaryBody()) + return systemObjects.get(coordinate.orbitNumber()).satelliteParameters.keys(); + + throw CelestialException("CelestialMasterDatabase::childOrbits called on improper type!"); +} + +List<CelestialCoordinate> CelestialMasterDatabase::scanSystems(RectI const& region, Maybe<StringSet> const& includedTypes) { + RecursiveMutexLocker locker(m_mutex); + + List<CelestialCoordinate> systems; + for (auto const& chunkLocation : chunkIndexesFor(region)) { + auto const& chunkData = getChunk(chunkLocation); + for (auto const& pair : chunkData.systemParameters) { + Vec3I systemLocation = pair.first; + if (region.contains(systemLocation.vec2())) { + if (includedTypes) { + String thisType = pair.second.getParameter("typeName", "").toString(); + if (!includedTypes->contains(thisType)) + continue; + } + systems.append(CelestialCoordinate(systemLocation)); + } + } + } + return systems; +} + +List<pair<Vec2I, Vec2I>> CelestialMasterDatabase::scanConstellationLines(RectI const& region) { + RecursiveMutexLocker locker(m_mutex); + + List<pair<Vec2I, Vec2I>> lines; + for (auto const& chunkLocation : chunkIndexesFor(region)) { + auto const& chunkData = getChunk(chunkLocation); + for (auto const& constellation : chunkData.constellations) { + for (auto const& line : constellation) { + if (region.intersects(Line2I(line.first, line.second))) + lines.append(line); + } + } + } + return lines; +} + +bool CelestialMasterDatabase::scanRegionFullyLoaded(RectI const&) { + return true; +} + +void CelestialMasterDatabase::updateParameters(CelestialCoordinate const& coordinate, CelestialParameters const& parameters) { + RecursiveMutexLocker locker(m_mutex); + + if (!coordinateValid(coordinate)) + throw CelestialException("CelestialMasterDatabase::updateParameters called on invalid coordinate"); + + auto chunkIndex = chunkIndexFor(coordinate); + auto chunk = getChunk(chunkIndex); + + bool updated = false; + if (coordinate.isSatelliteBody()) { + chunk.systemObjects.get(coordinate.location()) + .get(coordinate.parent().orbitNumber()) + .satelliteParameters.set(coordinate.orbitNumber(), parameters); + updated = true; + } else if (coordinate.isPlanetaryBody()) { + chunk.systemObjects.get(coordinate.location()).get(coordinate.orbitNumber()).planetParameters = parameters; + updated = true; + } + + if (updated && m_database.isOpen()) { + auto versioningDatabase = Root::singleton().versioningDatabase(); + auto versionedChunk = versioningDatabase->makeCurrentVersionedJson("CelestialChunk", chunk.toJson()); + m_database.insert(DataStreamBuffer::serialize(chunkIndex), compressData(DataStreamBuffer::serialize<VersionedJson>(versionedChunk))); + + m_chunkCache.remove(chunkIndex); + } else { + updated = false; + } + + if (!updated) + throw CelestialException("CelestialMasterDatabase::updateParameters failed; coordinate is not a valid planet or satellite, or celestial database was not open for writing"); +} + +Maybe<CelestialOrbitRegion> CelestialMasterDatabase::orbitRegion( + List<CelestialOrbitRegion> const& orbitRegions, int planetaryOrbitNumber) { + for (auto const& region : orbitRegions) { + if (planetaryOrbitNumber >= region.orbitRange[0] && planetaryOrbitNumber <= region.orbitRange[1]) + return region; + } + return {}; +} + +CelestialChunk const& CelestialMasterDatabase::getChunk(Vec2I const& chunkIndex) { + return m_chunkCache.get(chunkIndex, [this](Vec2I const& chunkIndex) -> CelestialChunk { + auto versioningDatabase = Root::singleton().versioningDatabase(); + + if (m_database.isOpen()) { + if (auto chunkData = m_database.find(DataStreamBuffer::serialize(chunkIndex))) { + auto versionedChunk = DataStreamBuffer::deserialize<VersionedJson>(uncompressData(chunkData.take())); + if (!versioningDatabase->versionedJsonCurrent(versionedChunk)) { + versionedChunk = versioningDatabase->updateVersionedJson(versionedChunk); + m_database.insert(DataStreamBuffer::serialize(chunkIndex), + compressData(DataStreamBuffer::serialize<VersionedJson>(versionedChunk))); + } + return CelestialChunk(versionedChunk.content); + } + } + + auto newChunk = produceChunk(chunkIndex); + if (m_database.isOpen()) { + auto versionedChunk = versioningDatabase->makeCurrentVersionedJson("CelestialChunk", newChunk.toJson()); + m_database.insert(DataStreamBuffer::serialize(chunkIndex), + compressData(DataStreamBuffer::serialize<VersionedJson>(versionedChunk))); + } + + return newChunk; + }); +} + +CelestialChunk CelestialMasterDatabase::produceChunk(Vec2I const& chunkIndex) const { + CelestialChunk chunkData; + chunkData.chunkIndex = chunkIndex; + + RandomSource random(staticRandomU64(chunkIndex[0], chunkIndex[1], "ChunkIndexMix")); + + RectI region = chunkRegion(chunkIndex); + List<Vec3I> systemLocations; + for (int x = region.xMin(); x < region.xMax(); ++x) { + for (int y = region.yMin(); y < region.yMax(); ++y) { + if (random.randf() < m_generationInformation.systemProbability) { + auto z = random.randi32() % (m_baseInformation.zCoordRange[1] - m_baseInformation.zCoordRange[0]) + + m_baseInformation.zCoordRange[0]; + systemLocations.append(Vec3I(x, y, z)); + } + } + } + + List<Vec2I> constellationCandidates; + for (auto const& systemLocation : systemLocations) { + if (auto systemInformation = produceSystem(random, systemLocation)) { + chunkData.systemParameters[systemLocation] = systemInformation.get().first; + chunkData.systemObjects[systemLocation] = move(systemInformation.get().second); + + if (systemInformation.get().first.getParameter("magnitude").toFloat() + >= m_generationInformation.minimumConstellationMagnitude) + constellationCandidates.append(systemLocation.vec2()); + } + } + + chunkData.constellations = produceConstellations(random, constellationCandidates); + + return chunkData; +} + +Maybe<pair<CelestialParameters, HashMap<int, CelestialPlanet>>> CelestialMasterDatabase::produceSystem( + RandomSource& random, Vec3I const& location) const { + float typeSelector = m_generationInformation.systemTypePerlin.get(location[0], location[1]); + String systemTypeName = binnedChoiceFromJson(m_generationInformation.systemTypeBins, typeSelector, "").toString(); + if (systemTypeName.empty()) + return {}; + auto systemType = m_generationInformation.systemTypes.get(systemTypeName); + + CelestialCoordinate systemCoordinate(location); + uint64_t systemSeed = random.randu64(); + + String prefix = m_generationInformation.systemPrefixNames.select(random); + String mid = m_generationInformation.systemNames.select(random); + String suffix = m_generationInformation.systemSuffixNames.select(random); + + String systemName = String(strf("%s %s %s", prefix, mid, suffix)).trim(); + + systemName = systemName.replace("<onedigit>", strf("%01d", random.randu32() % 10)); + systemName = systemName.replace("<twodigit>", strf("%02d", random.randu32() % 100)); + systemName = systemName.replace("<threedigit>", strf("%03d", random.randu32() % 1000)); + systemName = systemName.replace("<fourdigit>", strf("%04d", random.randu32() % 10000)); + + CelestialParameters systemParameters = CelestialParameters(systemCoordinate, + systemSeed, + systemName, + jsonMerge(systemType.baseParameters, random.randValueFrom(systemType.variationParameters))); + + List<int> planetaryOrbits; + for (int i = 1; i <= m_baseInformation.planetOrbitalLevels; ++i) { + if (auto systemOrbitRegion = orbitRegion(systemType.orbitRegions, i)) { + if (random.randf() <= systemOrbitRegion->bodyProbability) + planetaryOrbits.append(i); + } + } + + HashMap<int, CelestialPlanet> systemObjects; + for (auto planetPair : enumerateIterator(planetaryOrbits)) { + auto systemOrbitRegion = orbitRegion(systemType.orbitRegions, planetPair.first); + + auto planetaryTypeName = systemOrbitRegion->planetaryTypes.select(random); + if (m_generationInformation.planetaryTypes.contains(planetaryTypeName)) { + auto planetaryType = m_generationInformation.planetaryTypes.get(planetaryTypeName); + auto planetaryParameters = + jsonMerge(planetaryType.baseParameters, random.randValueFrom(planetaryType.variationParameters)); + + CelestialCoordinate planetCoordinate(location, planetPair.first); + uint64_t planetarySeed = random.randu64(); + String planetaryName = strf("%s %s", systemName, m_generationInformation.planetarySuffixes.at(planetPair.second)); + + CelestialPlanet planet; + planet.planetParameters = + CelestialParameters(planetCoordinate, planetarySeed, planetaryName, planetaryParameters); + + List<int> satelliteOrbits; + for (int i = 1; i <= m_baseInformation.satelliteOrbitalLevels; ++i) { + if (satelliteOrbits.size() < planetaryType.maxSatelliteCount + && random.randf() < planetaryType.satelliteProbability) + satelliteOrbits.append(i); + } + + for (auto satellitePair : enumerateIterator(satelliteOrbits)) { + auto satelliteTypeName = systemOrbitRegion->satelliteTypes.select(random); + if (m_generationInformation.satelliteTypes.contains(satelliteTypeName)) { + auto satelliteType = m_generationInformation.satelliteTypes.get(satelliteTypeName); + auto satelliteParameters = jsonMerge(satelliteType.baseParameters, + random.randValueFrom(satelliteType.variationParameters), + random.randValueFrom( + satelliteType.orbitParameters.value(systemOrbitRegion->regionName, JsonArray()).toArray())); + + CelestialCoordinate satelliteCoordinate(location, planetPair.first, satellitePair.first); + uint64_t satelliteSeed = random.randu64(); + String satelliteName = + strf("%s %s", planetaryName, m_generationInformation.satelliteSuffixes.at(satellitePair.second)); + + planet.satelliteParameters[satellitePair.first] = + CelestialParameters(satelliteCoordinate, satelliteSeed, satelliteName, satelliteParameters); + } + } + + systemObjects[planetPair.first] = move(planet); + } + } + + return pair<CelestialParameters, HashMap<int, CelestialPlanet>>{move(systemParameters), move(systemObjects)}; +} + +List<CelestialConstellation> CelestialMasterDatabase::produceConstellations( + RandomSource& random, List<Vec2I> const& constellationCandidates) const { + List<CelestialConstellation> constellations; + + if (random.randf() < m_generationInformation.constellationProbability && constellationCandidates.size() > 2) { + unsigned targetConstellationLineCount = random.randUInt( + m_generationInformation.constellationLineCountRange[0], m_generationInformation.constellationLineCountRange[1]); + Set<Vec2I> constellationPoints; + Set<Line2I> constellationLines; + + unsigned tries = 0; + + while (constellationLines.size() < targetConstellationLineCount) { + if (++tries > m_generationInformation.constellationMaxTries) + break; + + Vec2I start; + if (constellationPoints.empty()) + start = random.randValueFrom(constellationCandidates); + else + start = random.randValueFrom(constellationPoints); + + Vec2I end = random.randValueFrom(constellationCandidates); + + Line2I proposedLine(start, end); + Line2D proposedLineD(proposedLine); + + if (start == end) + continue; + + if (constellationLines.contains(proposedLine) || constellationLines.contains(proposedLine.reversed())) + continue; + + if (proposedLineD.diff().magnitude() > m_generationInformation.maximumConstellationLineLength) + continue; + + if (proposedLineD.diff().magnitude() < m_generationInformation.minimumConstellationLineLength) + continue; + + bool valid = true; + for (auto const& constellationLine : constellationLines) { + Line2D constellationLineD(constellationLine); + auto intersection = proposedLineD.intersection(constellationLineD); + if (intersection.intersects && Vec2I::round(intersection.point) != proposedLine.min() + && Vec2I::round(intersection.point) != proposedLine.max()) { + valid = false; + break; + } + + if (proposedLine.min() != constellationLine.min() && proposedLine.min() != constellationLine.max() + && constellationLineD.distanceTo(proposedLineD.min()) + < m_generationInformation.minimumConstellationLineCloseness) { + valid = false; + break; + } + + if (proposedLine.max() != constellationLine.min() && proposedLine.max() != constellationLine.max() + && constellationLineD.distanceTo(proposedLineD.max()) + < m_generationInformation.minimumConstellationLineCloseness) { + valid = false; + break; + } + } + + if (valid) { + constellationLines.add(proposedLine); + constellationPoints.add(proposedLine.min()); + constellationPoints.add(proposedLine.max()); + } + } + + if (constellationLines.size() > 1) { + CelestialConstellation constellation; + for (auto const& line : constellationLines) + constellation.append({line.min(), line.max()}); + constellations.append(constellation); + } + } + + return constellations; +} + +CelestialSlaveDatabase::CelestialSlaveDatabase(CelestialBaseInformation baseInformation) { + auto config = Root::singleton().assets()->json("/celestial.config"); + + m_baseInformation = move(baseInformation); + m_requestTimeout = config.getFloat("requestTimeout"); +} + +void CelestialSlaveDatabase::signalRegion(RectI const& region) { + RecursiveMutexLocker locker(m_mutex); + + for (auto location : chunkIndexesFor(region)) { + if (!m_chunkCache.ptr(location) && !m_pendingChunkRequests.contains(location)) + m_pendingChunkRequests[location] = Timer(); + } +} + +void CelestialSlaveDatabase::signalSystem(CelestialCoordinate const& system) { + RecursiveMutexLocker locker(m_mutex); + + if (auto chunk = m_chunkCache.ptr(chunkIndexFor(system))) { + if (!chunk->systemObjects.contains(system.location())) + m_pendingSystemRequests[system.location()] = Timer(); + } else { + signalRegion(RectI::withSize(system.location().vec2(), {1, 1})); + } +} + +List<CelestialRequest> CelestialSlaveDatabase::pullRequests() { + RecursiveMutexLocker locker(m_mutex); + + List<CelestialRequest> requests; + + auto chunkIt = makeSMutableMapIterator(m_pendingChunkRequests); + while (chunkIt.hasNext()) { + auto& pair = chunkIt.next(); + if (!pair.second.running()) { + requests.append(makeLeft(pair.first)); + pair.second.restart(m_requestTimeout); + } else if (pair.second.timeUp()) { + chunkIt.remove(); + } + } + + auto systemIt = makeSMutableMapIterator(m_pendingSystemRequests); + while (systemIt.hasNext()) { + auto& pair = systemIt.next(); + if (!pair.second.running()) { + requests.append(makeRight(pair.first)); + pair.second.restart(m_requestTimeout); + } else if (pair.second.timeUp()) { + systemIt.remove(); + } + } + + return requests; +} + +void CelestialSlaveDatabase::pushResponses(List<CelestialResponse> responses) { + RecursiveMutexLocker locker(m_mutex); + + for (auto& response : responses) { + if (auto celestialChunk = response.leftPtr()) { + m_pendingChunkRequests.remove(celestialChunk->chunkIndex); + m_chunkCache.set(celestialChunk->chunkIndex, move(*celestialChunk)); + } else if (auto celestialSystemObjects = response.rightPtr()) { + m_pendingSystemRequests.remove(celestialSystemObjects->systemLocation); + auto chunkLocation = chunkIndexFor(celestialSystemObjects->systemLocation); + if (auto chunk = m_chunkCache.ptr(chunkLocation)) + chunk->systemObjects[celestialSystemObjects->systemLocation] = move(celestialSystemObjects->planets); + } + } +} + +void CelestialSlaveDatabase::cleanup() { + RecursiveMutexLocker locker(m_mutex); + m_chunkCache.cleanup(); +} + +Maybe<CelestialParameters> CelestialSlaveDatabase::parameters(CelestialCoordinate const& coordinate) { + if (!coordinate) + throw CelestialException("CelestialSlaveDatabase::parameters called on null coordinate"); + + RecursiveMutexLocker locker(m_mutex); + + if (coordinate.isSystem()) + signalRegion(RectI::withSize(coordinate.location().vec2(), {1, 1})); + else + signalSystem(coordinate); + + if (auto chunk = m_chunkCache.ptr(chunkIndexFor(coordinate))) { + if (coordinate.isSystem()) + return chunk->systemParameters.maybe(coordinate.location()); + + if (auto systemObjects = chunk->systemObjects.ptr(coordinate.location())) { + auto const& planet = systemObjects->get(coordinate.planet().orbitNumber()); + if (coordinate.isPlanetaryBody()) + return planet.planetParameters; + else if (coordinate.isSatelliteBody()) + return planet.satelliteParameters.get(coordinate.orbitNumber()); + } + } + + return {}; +} + +Maybe<String> CelestialSlaveDatabase::name(CelestialCoordinate const& coordinate) { + if (auto p = parameters(coordinate)) + return p->name(); + return {}; +} + +Maybe<bool> CelestialSlaveDatabase::hasChildren(CelestialCoordinate const& coordinate) { + if (!coordinate) + throw CelestialException("CelestialSlaveDatabase::hasChildren called on null coordinate"); + + RecursiveMutexLocker locker(m_mutex); + + signalSystem(coordinate); + + if (auto chunk = m_chunkCache.ptr(chunkIndexFor(coordinate))) { + if (auto systemObjects = chunk->systemObjects.ptr(coordinate.location())) { + if (coordinate.isSystem()) + return !systemObjects->empty(); + else if (coordinate.isPlanetaryBody()) + return !systemObjects->get(coordinate.orbitNumber()).satelliteParameters.empty(); + } + } + + return {}; +} + +List<CelestialCoordinate> CelestialSlaveDatabase::children(CelestialCoordinate const& coordinate) { + return childOrbits(coordinate).transformed(bind(&CelestialCoordinate::child, coordinate, _1)); +} + +List<int> CelestialSlaveDatabase::childOrbits(CelestialCoordinate const& coordinate) { + if (!coordinate) + throw CelestialException("CelestialSlaveDatabase::childOrbits called on null coordinate"); + + if (coordinate.isSatelliteBody()) + throw CelestialException("CelestialSlaveDatabase::childOrbits called on improper type!"); + + RecursiveMutexLocker locker(m_mutex); + + signalSystem(coordinate); + + if (auto chunk = m_chunkCache.ptr(chunkIndexFor(coordinate))) { + if (auto systemObjects = chunk->systemObjects.ptr(coordinate.location())) { + if (coordinate.isSystem()) + return systemObjects->keys(); + else if (coordinate.isPlanetaryBody()) + return systemObjects->get(coordinate.orbitNumber()).satelliteParameters.keys(); + } + } + + return {}; +} + +List<CelestialCoordinate> CelestialSlaveDatabase::scanSystems(RectI const& region, Maybe<StringSet> const& includedTypes) { + RecursiveMutexLocker locker(m_mutex); + + signalRegion(region); + + List<CelestialCoordinate> systems; + for (auto const& chunkLocation : chunkIndexesFor(region)) { + if (auto chunkData = m_chunkCache.ptr(chunkLocation)) { + for (auto const& pair : chunkData->systemParameters) { + Vec3I systemLocation = pair.first; + if (region.contains(systemLocation.vec2())) { + if (includedTypes) { + String thisType = pair.second.getParameter("typeName", "").toString(); + if (!includedTypes->contains(thisType)) + continue; + } + systems.append(CelestialCoordinate(systemLocation)); + } + } + } + } + return systems; +} + +List<pair<Vec2I, Vec2I>> CelestialSlaveDatabase::scanConstellationLines(RectI const& region) { + RecursiveMutexLocker locker(m_mutex); + + signalRegion(region); + + List<pair<Vec2I, Vec2I>> lines; + for (auto const& chunkLocation : chunkIndexesFor(region)) { + if (auto chunkData = m_chunkCache.ptr(chunkLocation)) { + for (auto const& constellation : chunkData->constellations) { + for (auto const& line : constellation) { + if (region.intersects(Line2I(line.first, line.second))) + lines.append(line); + } + } + } + } + return lines; +} + +bool CelestialSlaveDatabase::scanRegionFullyLoaded(RectI const& region) { + RecursiveMutexLocker locker(m_mutex); + + signalRegion(region); + + for (auto const& chunkLocation : chunkIndexesFor(region)) { + if (!m_chunkCache.ptr(chunkLocation)) + return false; + } + return true; +} + +void CelestialSlaveDatabase::invalidateCacheFor(CelestialCoordinate const& coordinate) { + RecursiveMutexLocker locker(m_mutex); + + m_chunkCache.remove(chunkIndexFor(coordinate)); +} + +} |