829 lines
32 KiB
C++
829 lines
32 KiB
C++
|
#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));
|
||
|
}
|
||
|
|
||
|
}
|