702 lines
26 KiB
C++
702 lines
26 KiB
C++
|
#include "StarRoot.hpp"
|
||
|
#include "StarIterator.hpp"
|
||
|
#include "StarJsonExtra.hpp"
|
||
|
#include "StarFile.hpp"
|
||
|
#include "StarEncode.hpp"
|
||
|
#include "StarConfiguration.hpp"
|
||
|
#include "StarAssets.hpp"
|
||
|
#include "StarItemDatabase.hpp"
|
||
|
#include "StarMaterialDatabase.hpp"
|
||
|
#include "StarTerrainDatabase.hpp"
|
||
|
#include "StarBiomeDatabase.hpp"
|
||
|
#include "StarLiquidsDatabase.hpp"
|
||
|
#include "StarStatusEffectDatabase.hpp"
|
||
|
#include "StarDamageDatabase.hpp"
|
||
|
#include "StarParticleDatabase.hpp"
|
||
|
#include "StarProjectile.hpp"
|
||
|
#include "StarMonster.hpp"
|
||
|
#include "StarNpc.hpp"
|
||
|
#include "StarObject.hpp"
|
||
|
#include "StarPlant.hpp"
|
||
|
#include "StarPlantDrop.hpp"
|
||
|
#include "StarStagehandDatabase.hpp"
|
||
|
#include "StarVehicleDatabase.hpp"
|
||
|
#include "StarPlayer.hpp"
|
||
|
#include "StarItemDrop.hpp"
|
||
|
#include "StarEffectSourceDatabase.hpp"
|
||
|
#include "StarStoredFunctions.hpp"
|
||
|
#include "StarTreasure.hpp"
|
||
|
#include "StarDungeonGenerator.hpp"
|
||
|
#include "StarTilesetDatabase.hpp"
|
||
|
#include "StarStatisticsDatabase.hpp"
|
||
|
#include "StarEmoteProcessor.hpp"
|
||
|
#include "StarSpeciesDatabase.hpp"
|
||
|
#include "StarImageMetadataDatabase.hpp"
|
||
|
#include "StarLogging.hpp"
|
||
|
#include "StarProjectileDatabase.hpp"
|
||
|
#include "StarPlayerFactory.hpp"
|
||
|
#include "StarObjectDatabase.hpp"
|
||
|
#include "StarEntityFactory.hpp"
|
||
|
#include "StarDirectoryAssetSource.hpp"
|
||
|
#include "StarPackedAssetSource.hpp"
|
||
|
#include "StarJsonBuilder.hpp"
|
||
|
#include "StarQuestTemplateDatabase.hpp"
|
||
|
#include "StarAiDatabase.hpp"
|
||
|
#include "StarTechDatabase.hpp"
|
||
|
#include "StarWorkerPool.hpp"
|
||
|
#include "StarCodexDatabase.hpp"
|
||
|
#include "StarBehaviorDatabase.hpp"
|
||
|
#include "StarTenantDatabase.hpp"
|
||
|
#include "StarNameGenerator.hpp"
|
||
|
#include "StarDanceDatabase.hpp"
|
||
|
#include "StarSpawnTypeDatabase.hpp"
|
||
|
#include "StarRadioMessageDatabase.hpp"
|
||
|
#include "StarCollectionDatabase.hpp"
|
||
|
|
||
|
namespace Star {
|
||
|
|
||
|
namespace {
|
||
|
unsigned const RootMaintenanceSleep = 5000;
|
||
|
unsigned const RootLoadThreads = 2;
|
||
|
}
|
||
|
|
||
|
atomic<Root*> Root::s_singleton;
|
||
|
|
||
|
Root* Root::singletonPtr() {
|
||
|
return s_singleton.load();
|
||
|
}
|
||
|
|
||
|
Root& Root::singleton() {
|
||
|
auto ptr = s_singleton.load();
|
||
|
if (!ptr)
|
||
|
throw RootException("Root::singleton() called with no Root instance available");
|
||
|
else
|
||
|
return *ptr;
|
||
|
}
|
||
|
|
||
|
Root::Root(Settings settings) {
|
||
|
Root* oldRoot = nullptr;
|
||
|
if (!s_singleton.compare_exchange_strong(oldRoot, this))
|
||
|
throw RootException("Singleton Root has been constructed twice");
|
||
|
|
||
|
m_settings = move(settings);
|
||
|
if (m_settings.runtimeConfigFile)
|
||
|
m_runtimeConfigFile = toStoragePath(*m_settings.runtimeConfigFile);
|
||
|
|
||
|
if (!File::isDirectory(m_settings.storageDirectory))
|
||
|
File::makeDirectory(m_settings.storageDirectory);
|
||
|
|
||
|
if (m_settings.logFile) {
|
||
|
String logFile = toStoragePath(*m_settings.logFile);
|
||
|
File::backupFileInSequence(logFile, m_settings.logFileBackups);
|
||
|
Logger::addSink(make_shared<FileLogSink>(logFile, m_settings.logLevel, true));
|
||
|
}
|
||
|
Logger::stdoutSink()->setLevel(m_settings.logLevel);
|
||
|
|
||
|
if (m_settings.quiet)
|
||
|
Logger::removeStdoutSink();
|
||
|
|
||
|
Logger::info("Root: Preparing Root...");
|
||
|
|
||
|
m_stopMaintenanceThread = false;
|
||
|
m_maintenanceThread = Thread::invoke("Root::maintenanceMain", [this]() {
|
||
|
MutexLocker locker(m_maintenanceStopMutex);
|
||
|
while (!m_stopMaintenanceThread) {
|
||
|
m_reloadListeners.clearExpiredListeners();
|
||
|
|
||
|
{
|
||
|
MutexLocker locker(m_objectDatabaseMutex);
|
||
|
if (m_objectDatabase)
|
||
|
m_objectDatabase->cleanup();
|
||
|
}
|
||
|
{
|
||
|
MutexLocker locker(m_monsterDatabaseMutex);
|
||
|
if (m_monsterDatabase)
|
||
|
m_monsterDatabase->cleanup();
|
||
|
}
|
||
|
{
|
||
|
MutexLocker locker(m_assetsMutex);
|
||
|
if (m_assets)
|
||
|
m_assets->cleanup();
|
||
|
}
|
||
|
{
|
||
|
MutexLocker locker(m_tenantDatabaseMutex);
|
||
|
if (m_tenantDatabase)
|
||
|
m_tenantDatabase->cleanup();
|
||
|
}
|
||
|
|
||
|
Random::addEntropy();
|
||
|
|
||
|
{
|
||
|
MutexLocker locker(m_configurationMutex);
|
||
|
writeConfig();
|
||
|
}
|
||
|
|
||
|
m_maintenanceStopCondition.wait(m_maintenanceStopMutex, RootMaintenanceSleep);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
Logger::info("Root: Done preparing Root.");
|
||
|
}
|
||
|
|
||
|
Root::~Root() {
|
||
|
Logger::info("Root: Shutting down Root");
|
||
|
|
||
|
{
|
||
|
MutexLocker locker(m_maintenanceStopMutex);
|
||
|
m_stopMaintenanceThread = true;
|
||
|
m_maintenanceStopCondition.signal();
|
||
|
}
|
||
|
m_maintenanceThread.finish();
|
||
|
|
||
|
m_reloadListeners.clearAllListeners();
|
||
|
|
||
|
writeConfig();
|
||
|
|
||
|
s_singleton.store(nullptr);
|
||
|
}
|
||
|
|
||
|
void Root::reload() {
|
||
|
Logger::info("Root: Reloading from disk");
|
||
|
|
||
|
{
|
||
|
// We need to lock all the mutexes to reset everything to cause it to be
|
||
|
// reloaded, but whenever we lock individual members we should always do it
|
||
|
// in the same order (well, the same order*ing* not necessarily the same
|
||
|
// order) to avoid deadlocks. This means that we need to enumerate the
|
||
|
// finicky, implicit dependency order that we have due to each member's
|
||
|
// constructor referencing root recursively. We could avoid doing this
|
||
|
// explicitly with C++11's std::lock (if c++11 threading primitives were
|
||
|
// finally reliable on all targets), or some other equivalent deadlock
|
||
|
// avoidance algorithm.
|
||
|
|
||
|
// Entity factory depends on all the entity databases and the versioning
|
||
|
// database.
|
||
|
MutexLocker entityFactoryLock(m_entityFactoryMutex);
|
||
|
|
||
|
// Species database depends on the item database.
|
||
|
MutexLocker speciesDatabaseLock(m_speciesDatabaseMutex);
|
||
|
|
||
|
// Item database depends on object database and codex database
|
||
|
MutexLocker itemDatabaseLock(m_itemDatabaseMutex);
|
||
|
|
||
|
// These databases depend on various things below, but not the item database
|
||
|
MutexLocker objectDatabaseLock(m_objectDatabaseMutex);
|
||
|
MutexLocker playerFactoryLock(m_playerFactoryMutex);
|
||
|
MutexLocker npcDatabaseLock(m_npcDatabaseMutex);
|
||
|
MutexLocker stagehandDatabaseLock(m_stagehandDatabaseMutex);
|
||
|
MutexLocker vehicleDatabaseLock(m_vehicleDatabaseMutex);
|
||
|
MutexLocker monsterDatabaseLock(m_monsterDatabaseMutex);
|
||
|
MutexLocker plantDatabaseLock(m_plantDatabaseMutex);
|
||
|
MutexLocker projectileDatabaseLock(m_projectileDatabaseMutex);
|
||
|
|
||
|
// Biome database depends on liquids, materials, and stored function
|
||
|
// databases.
|
||
|
MutexLocker biomeDatabaseLock(m_biomeDatabaseMutex);
|
||
|
|
||
|
// Dungeon definitions database depends on the material and liquids database
|
||
|
MutexLocker dungeonDefinitionsLock(m_dungeonDefinitionsMutex);
|
||
|
MutexLocker tilesetDatabaseLock(m_tilesetDatabaseMutex);
|
||
|
|
||
|
MutexLocker statisticsDatabaseLock(m_statisticsDatabaseMutex);
|
||
|
|
||
|
// Liquids database depends on the materials database
|
||
|
MutexLocker liquidsDatabaseLock(m_liquidsDatabaseMutex);
|
||
|
|
||
|
// Material database depends on particle database
|
||
|
MutexLocker materialDatabaseLock(m_materialDatabaseMutex);
|
||
|
|
||
|
// Databases that depend on functions database.
|
||
|
MutexLocker damageDatabaseLock(m_damageDatabaseMutex);
|
||
|
MutexLocker effectSourceDatabaseLock(m_effectSourceDatabaseMutex);
|
||
|
MutexLocker statusEffectDatabaseLock(m_statusEffectDatabaseMutex);
|
||
|
MutexLocker treasureDatabaseLock(m_treasureDatabaseMutex);
|
||
|
|
||
|
// Databases that don't depend on anything other than assets
|
||
|
MutexLocker codexDatabaseLock(m_codexDatabaseMutex);
|
||
|
MutexLocker behaviorDatabaseMutex(m_behaviorDatabaseMutex);
|
||
|
MutexLocker techDatabaseLock(m_techDatabaseMutex);
|
||
|
MutexLocker aiDatabaseLock(m_aiDatabaseMutex);
|
||
|
MutexLocker questTemplateDatabaseLock(m_questTemplateDatabaseMutex);
|
||
|
MutexLocker emoteProcessorLock(m_emoteProcessorMutex);
|
||
|
MutexLocker terrainDatabaseLock(m_terrainDatabaseMutex);
|
||
|
MutexLocker particleDatabaseLock(m_particleDatabaseMutex);
|
||
|
MutexLocker versioningDatabaseLock(m_versioningDatabaseMutex);
|
||
|
MutexLocker functionDatabaseLock(m_functionDatabaseMutex);
|
||
|
MutexLocker imageMetadataDatabaseLock(m_imageMetadataDatabaseMutex);
|
||
|
MutexLocker tenantDatabaseLock(m_tenantDatabaseMutex);
|
||
|
MutexLocker nameGeneratorLock(m_nameGeneratorMutex);
|
||
|
MutexLocker danceDatabaseLock(m_danceDatabaseMutex);
|
||
|
MutexLocker spawnTypeDatabaseLock(m_spawnTypeDatabaseMutex);
|
||
|
MutexLocker radioMessageDatabaseLock(m_radioMessageDatabaseMutex);
|
||
|
MutexLocker collectionDatabaseLock(m_collectionDatabaseMutex);
|
||
|
|
||
|
// Configuration and Assets are at the very bottom of the hierarchy.
|
||
|
MutexLocker configurationLock(m_configurationMutex);
|
||
|
MutexLocker assetsLock(m_assetsMutex);
|
||
|
|
||
|
writeConfig();
|
||
|
|
||
|
m_entityFactory.reset();
|
||
|
m_speciesDatabase.reset();
|
||
|
m_itemDatabase.reset();
|
||
|
m_objectDatabase.reset();
|
||
|
m_playerFactory.reset();
|
||
|
m_stagehandDatabase.reset();
|
||
|
m_vehicleDatabase.reset();
|
||
|
m_npcDatabase.reset();
|
||
|
m_monsterDatabase.reset();
|
||
|
m_plantDatabase.reset();
|
||
|
m_projectileDatabase.reset();
|
||
|
m_biomeDatabase.reset();
|
||
|
m_dungeonDefinitions.reset();
|
||
|
m_tilesetDatabase.reset();
|
||
|
m_statisticsDatabase.reset();
|
||
|
m_liquidsDatabase.reset();
|
||
|
m_materialDatabase.reset();
|
||
|
m_damageDatabase.reset();
|
||
|
m_effectSourceDatabase.reset();
|
||
|
m_statusEffectDatabase.reset();
|
||
|
m_treasureDatabase.reset();
|
||
|
m_codexDatabase.reset();
|
||
|
m_behaviorDatabase.reset();
|
||
|
m_techDatabase.reset();
|
||
|
m_aiDatabase.reset();
|
||
|
m_questTemplateDatabase.reset();
|
||
|
m_emoteProcessor.reset();
|
||
|
m_terrainDatabase.reset();
|
||
|
m_particleDatabase.reset();
|
||
|
m_versioningDatabase.reset();
|
||
|
m_functionDatabase.reset();
|
||
|
m_imageMetadataDatabase.reset();
|
||
|
m_tenantDatabase.reset();
|
||
|
m_nameGenerator.reset();
|
||
|
m_danceDatabase.reset();
|
||
|
m_spawnTypeDatabase.reset();
|
||
|
m_radioMessageDatabase.reset();
|
||
|
m_collectionDatabase.reset();
|
||
|
m_assets.reset();
|
||
|
m_configuration.reset();
|
||
|
}
|
||
|
|
||
|
m_reloadListeners.trigger();
|
||
|
}
|
||
|
|
||
|
void Root::reloadWithMods(StringList modDirectories) {
|
||
|
MutexLocker locker(m_modsMutex);
|
||
|
m_modDirectories = move(modDirectories);
|
||
|
reload();
|
||
|
}
|
||
|
|
||
|
void Root::fullyLoad() {
|
||
|
auto workerPool = WorkerPool("Root::fullyLoad", RootLoadThreads);
|
||
|
List<WorkerPoolHandle> loaders;
|
||
|
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::assets, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::configuration, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::nameGenerator, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::objectDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::plantDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::projectileDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::monsterDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::npcDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::stagehandDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::vehicleDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::playerFactory, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::entityFactory, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::itemDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::materialDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::terrainDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::biomeDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::liquidsDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::statusEffectDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::damageDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::particleDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::effectSourceDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::functionDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::treasureDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::dungeonDefinitions, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::tilesetDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::statisticsDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::emoteProcessor, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::speciesDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::imageMetadataDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::versioningDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::questTemplateDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::aiDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::techDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::codexDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::behaviorDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::danceDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::spawnTypeDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::radioMessageDatabase, this))));
|
||
|
loaders.append(workerPool.addWork(swallow(bind(&Root::collectionDatabase, this))));
|
||
|
|
||
|
for (auto& loader : loaders)
|
||
|
loader.finish();
|
||
|
|
||
|
{
|
||
|
MutexLocker locker(m_assetsMutex);
|
||
|
if (m_assets)
|
||
|
m_assets->clearCache();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Root::registerReloadListener(ListenerWeakPtr reloadListener) {
|
||
|
m_reloadListeners.addListener(move(reloadListener));
|
||
|
}
|
||
|
|
||
|
String Root::toStoragePath(String const& path) const {
|
||
|
return File::relativeTo(m_settings.storageDirectory, File::convertDirSeparators(path));
|
||
|
}
|
||
|
|
||
|
AssetsConstPtr Root::assets() {
|
||
|
return loadMemberFunction<Assets>(m_assets, m_assetsMutex, "Assets", [this]() {
|
||
|
StringList assetDirectories = m_settings.assetDirectories;
|
||
|
assetDirectories.appendAll(m_modDirectories);
|
||
|
|
||
|
auto assets = make_shared<Assets>(m_settings.assetsSettings, scanForAssetSources(assetDirectories));
|
||
|
Logger::info("Assets digest is %s", hexEncode(assets->digest()));
|
||
|
return assets;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ConfigurationPtr Root::configuration() {
|
||
|
return loadMemberFunction<Configuration>(m_configuration, m_configurationMutex, "Configuration", [this]() {
|
||
|
Json currentConfig;
|
||
|
|
||
|
if (m_runtimeConfigFile) {
|
||
|
if (!File::isFile(*m_runtimeConfigFile)) {
|
||
|
Logger::info("Root: no runtime config file, creating new default runtime config");
|
||
|
currentConfig = m_settings.defaultConfiguration;
|
||
|
} else {
|
||
|
try {
|
||
|
Json config = Json::parseJson(File::readFileString(*m_runtimeConfigFile));
|
||
|
if (!config.isType(Json::Type::Object))
|
||
|
throw ConfigurationException("User config is not of JSON type Object");
|
||
|
|
||
|
if (config.get("configurationVersion", {}) != m_settings.defaultConfiguration.get("configurationVersion", {}))
|
||
|
throw ConfigurationException("User config version does not match default config version");
|
||
|
|
||
|
currentConfig = config;
|
||
|
} catch (std::exception const& e) {
|
||
|
Logger::warn("Root: Failed to load user configuration file %s, resetting user config: %s", *m_runtimeConfigFile, outputException(e, false));
|
||
|
currentConfig = m_settings.defaultConfiguration;
|
||
|
File::rename(*m_runtimeConfigFile, *m_runtimeConfigFile + ".old");
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
currentConfig = m_settings.defaultConfiguration;
|
||
|
}
|
||
|
|
||
|
return make_shared<Configuration>(m_settings.defaultConfiguration, currentConfig);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ObjectDatabaseConstPtr Root::objectDatabase() {
|
||
|
return loadMember(m_objectDatabase, m_objectDatabaseMutex, "ObjectDatabase");
|
||
|
}
|
||
|
|
||
|
PlantDatabaseConstPtr Root::plantDatabase() {
|
||
|
return loadMember(m_plantDatabase, m_plantDatabaseMutex, "PlantDatabase");
|
||
|
}
|
||
|
|
||
|
ProjectileDatabaseConstPtr Root::projectileDatabase() {
|
||
|
return loadMember(m_projectileDatabase, m_projectileDatabaseMutex, "ProjectileDatabase");
|
||
|
}
|
||
|
|
||
|
MonsterDatabaseConstPtr Root::monsterDatabase() {
|
||
|
return loadMember(m_monsterDatabase, m_monsterDatabaseMutex, "MonsterDatabase");
|
||
|
}
|
||
|
|
||
|
NpcDatabaseConstPtr Root::npcDatabase() {
|
||
|
return loadMember(m_npcDatabase, m_npcDatabaseMutex, "NpcDatabase");
|
||
|
}
|
||
|
|
||
|
StagehandDatabaseConstPtr Root::stagehandDatabase() {
|
||
|
return loadMember(m_stagehandDatabase, m_stagehandDatabaseMutex, "StagehandDatabase");
|
||
|
}
|
||
|
|
||
|
VehicleDatabaseConstPtr Root::vehicleDatabase() {
|
||
|
return loadMember(m_vehicleDatabase, m_vehicleDatabaseMutex, "VehicleDatabase");
|
||
|
}
|
||
|
|
||
|
PlayerFactoryConstPtr Root::playerFactory() {
|
||
|
return loadMember(m_playerFactory, m_playerFactoryMutex, "PlayerFactory");
|
||
|
}
|
||
|
|
||
|
EntityFactoryConstPtr Root::entityFactory() {
|
||
|
return loadMember(m_entityFactory, m_entityFactoryMutex, "EntityFactory");
|
||
|
}
|
||
|
|
||
|
PatternedNameGeneratorConstPtr Root::nameGenerator() {
|
||
|
return loadMember(m_nameGenerator, m_nameGeneratorMutex, "NameGenerator");
|
||
|
}
|
||
|
|
||
|
ItemDatabaseConstPtr Root::itemDatabase() {
|
||
|
return loadMember(m_itemDatabase, m_itemDatabaseMutex, "ItemDatabase");
|
||
|
}
|
||
|
|
||
|
MaterialDatabaseConstPtr Root::materialDatabase() {
|
||
|
return loadMember(m_materialDatabase, m_materialDatabaseMutex, "MaterialDatabase");
|
||
|
}
|
||
|
|
||
|
TerrainDatabaseConstPtr Root::terrainDatabase() {
|
||
|
return loadMember(m_terrainDatabase, m_terrainDatabaseMutex, "TerrainDatabase");
|
||
|
}
|
||
|
|
||
|
BiomeDatabaseConstPtr Root::biomeDatabase() {
|
||
|
return loadMember(m_biomeDatabase, m_biomeDatabaseMutex, "BiomeDatabase");
|
||
|
}
|
||
|
|
||
|
LiquidsDatabaseConstPtr Root::liquidsDatabase() {
|
||
|
return loadMember(m_liquidsDatabase, m_liquidsDatabaseMutex, "LiquidsDatabase");
|
||
|
}
|
||
|
|
||
|
StatusEffectDatabaseConstPtr Root::statusEffectDatabase() {
|
||
|
return loadMember(m_statusEffectDatabase, m_statusEffectDatabaseMutex, "StatusEffectDatabase");
|
||
|
}
|
||
|
|
||
|
DamageDatabaseConstPtr Root::damageDatabase() {
|
||
|
return loadMember(m_damageDatabase, m_damageDatabaseMutex, "DamageDatabase");
|
||
|
}
|
||
|
|
||
|
ParticleDatabaseConstPtr Root::particleDatabase() {
|
||
|
return loadMember(m_particleDatabase, m_particleDatabaseMutex, "ParticleDatabase");
|
||
|
}
|
||
|
|
||
|
EffectSourceDatabaseConstPtr Root::effectSourceDatabase() {
|
||
|
return loadMember(m_effectSourceDatabase, m_effectSourceDatabaseMutex, "EffectSourceDatabase");
|
||
|
}
|
||
|
|
||
|
FunctionDatabaseConstPtr Root::functionDatabase() {
|
||
|
return loadMember(m_functionDatabase, m_functionDatabaseMutex, "FunctionDatabase");
|
||
|
}
|
||
|
|
||
|
TreasureDatabaseConstPtr Root::treasureDatabase() {
|
||
|
return loadMember(m_treasureDatabase, m_treasureDatabaseMutex, "TreasureDatabase");
|
||
|
}
|
||
|
|
||
|
DungeonDefinitionsConstPtr Root::dungeonDefinitions() {
|
||
|
return loadMember(m_dungeonDefinitions, m_dungeonDefinitionsMutex, "DungeonDefinitions");
|
||
|
}
|
||
|
|
||
|
TilesetDatabaseConstPtr Root::tilesetDatabase() {
|
||
|
return loadMember(m_tilesetDatabase, m_tilesetDatabaseMutex, "TilesetDatabase");
|
||
|
}
|
||
|
|
||
|
StatisticsDatabaseConstPtr Root::statisticsDatabase() {
|
||
|
return loadMember(m_statisticsDatabase, m_statisticsDatabaseMutex, "StatisticsDatabase");
|
||
|
}
|
||
|
|
||
|
EmoteProcessorConstPtr Root::emoteProcessor() {
|
||
|
return loadMember(m_emoteProcessor, m_emoteProcessorMutex, "EmoteProcessor");
|
||
|
}
|
||
|
|
||
|
SpeciesDatabaseConstPtr Root::speciesDatabase() {
|
||
|
return loadMember(m_speciesDatabase, m_speciesDatabaseMutex, "SpeciesDatabase");
|
||
|
}
|
||
|
|
||
|
ImageMetadataDatabaseConstPtr Root::imageMetadataDatabase() {
|
||
|
return loadMember(m_imageMetadataDatabase, m_imageMetadataDatabaseMutex, "ImageMetadataDatabase");
|
||
|
}
|
||
|
|
||
|
VersioningDatabaseConstPtr Root::versioningDatabase() {
|
||
|
return loadMember(m_versioningDatabase, m_versioningDatabaseMutex, "VersioningDatabase");
|
||
|
}
|
||
|
|
||
|
QuestTemplateDatabaseConstPtr Root::questTemplateDatabase() {
|
||
|
return loadMember(m_questTemplateDatabase, m_questTemplateDatabaseMutex, "QuestTemplateDatabase");
|
||
|
}
|
||
|
|
||
|
AiDatabaseConstPtr Root::aiDatabase() {
|
||
|
return loadMember(m_aiDatabase, m_aiDatabaseMutex, "AiDatabase");
|
||
|
}
|
||
|
|
||
|
TechDatabaseConstPtr Root::techDatabase() {
|
||
|
return loadMember(m_techDatabase, m_techDatabaseMutex, "TechDatabase");
|
||
|
}
|
||
|
|
||
|
CodexDatabaseConstPtr Root::codexDatabase() {
|
||
|
return loadMember(m_codexDatabase, m_codexDatabaseMutex, "CodexDatabase");
|
||
|
}
|
||
|
|
||
|
BehaviorDatabaseConstPtr Root::behaviorDatabase() {
|
||
|
return loadMember(m_behaviorDatabase, m_behaviorDatabaseMutex, "BehaviorDatabase");
|
||
|
}
|
||
|
|
||
|
TenantDatabaseConstPtr Root::tenantDatabase() {
|
||
|
return loadMember(m_tenantDatabase, m_tenantDatabaseMutex, "TenantDatabase");
|
||
|
}
|
||
|
|
||
|
DanceDatabaseConstPtr Root::danceDatabase() {
|
||
|
return loadMember(m_danceDatabase, m_danceDatabaseMutex, "DanceDatabase");
|
||
|
}
|
||
|
|
||
|
SpawnTypeDatabaseConstPtr Root::spawnTypeDatabase() {
|
||
|
return loadMember(m_spawnTypeDatabase, m_spawnTypeDatabaseMutex, "SpawnTypeDatabase");
|
||
|
}
|
||
|
|
||
|
RadioMessageDatabaseConstPtr Root::radioMessageDatabase() {
|
||
|
return loadMember(m_radioMessageDatabase, m_radioMessageDatabaseMutex, "RadioMessageDatabase");
|
||
|
}
|
||
|
|
||
|
CollectionDatabaseConstPtr Root::collectionDatabase() {
|
||
|
return loadMember(m_collectionDatabase, m_collectionDatabaseMutex, "CollectionDatabase");
|
||
|
}
|
||
|
|
||
|
StringList Root::scanForAssetSources(StringList const& directories) {
|
||
|
struct AssetSource {
|
||
|
String path;
|
||
|
Maybe<String> name;
|
||
|
float priority;
|
||
|
StringList requires;
|
||
|
StringList includes;
|
||
|
};
|
||
|
List<shared_ptr<AssetSource>> assetSources;
|
||
|
StringMap<shared_ptr<AssetSource>> namedSources;
|
||
|
|
||
|
// Scan for assets in each given directory, the first-level ordering of asset
|
||
|
// sources comes from the scanning order here, and then alphabetically by the
|
||
|
// file / directory name
|
||
|
|
||
|
for (auto const& directory : directories) {
|
||
|
if (!File::isDirectory(directory)) {
|
||
|
Logger::info("Root: Skipping asset directory '%s', directory not found", directory);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
Logger::info("Root: Scanning for asset sources in directory '%s'", directory);
|
||
|
|
||
|
for (auto entry : File::dirList(directory, true).sorted()) {
|
||
|
AssetSourcePtr source;
|
||
|
auto fileName = File::relativeTo(directory, entry.first);
|
||
|
if (entry.first.beginsWith(".") || entry.first.beginsWith("_"))
|
||
|
Logger::info("Root: Skipping hidden '%s' in asset directory", entry.first);
|
||
|
else if (entry.second)
|
||
|
source = make_shared<DirectoryAssetSource>(fileName);
|
||
|
else if (entry.first.endsWith(".pak"))
|
||
|
source = make_shared<PackedAssetSource>(fileName);
|
||
|
else
|
||
|
Logger::warn("Root: Unrecognized file in asset directory '%s', skipping", entry.first);
|
||
|
|
||
|
if (!source)
|
||
|
continue;
|
||
|
|
||
|
auto metadata = source->metadata();
|
||
|
|
||
|
auto assetSource = make_shared<AssetSource>();
|
||
|
assetSource->path = fileName;
|
||
|
assetSource->name = metadata.maybe("name").apply(mem_fn(&Json::toString));
|
||
|
assetSource->priority = metadata.value("priority", 0.0f).toFloat();
|
||
|
assetSource->requires = jsonToStringList(metadata.value("requires", JsonArray{}));
|
||
|
assetSource->includes = jsonToStringList(metadata.value("includes", JsonArray{}));
|
||
|
|
||
|
if (assetSource->name) {
|
||
|
if (auto oldAssetSource = namedSources.value(*assetSource->name)) {
|
||
|
if (oldAssetSource->priority <= assetSource->priority) {
|
||
|
Logger::warn("Root: Overriding duplicate asset source '%s' named '%s' with higher or equal priority source '%s",
|
||
|
oldAssetSource->path, *assetSource->name, assetSource->path);
|
||
|
*oldAssetSource = *assetSource;
|
||
|
} else {
|
||
|
Logger::warn("Root: Skipping duplicate asset source '%s' named '%s', previous source '%s' has higher priority",
|
||
|
assetSource->path, *assetSource->name, oldAssetSource->priority);
|
||
|
}
|
||
|
} else {
|
||
|
namedSources[*assetSource->name] = assetSource;
|
||
|
assetSources.append(move(assetSource));
|
||
|
}
|
||
|
} else {
|
||
|
assetSources.append(move(assetSource));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Then, order asset sources so that lower priority assets come before higher
|
||
|
// priority ones
|
||
|
|
||
|
assetSources.sort([](auto const& a, auto const& b) {
|
||
|
return a->priority < b->priority;
|
||
|
});
|
||
|
|
||
|
// Finally, sort asset sources so that sources that have dependencies come
|
||
|
// after their dependencies.
|
||
|
|
||
|
HashSet<shared_ptr<AssetSource>> workingSet;
|
||
|
OrderedHashSet<shared_ptr<AssetSource>> dependencySortedSources;
|
||
|
|
||
|
function<void(shared_ptr<AssetSource>)> dependencySortVisit;
|
||
|
dependencySortVisit = [&](shared_ptr<AssetSource> source) {
|
||
|
if (workingSet.contains(source))
|
||
|
throw StarException("Asset dependencies form a cycle");
|
||
|
|
||
|
if (dependencySortedSources.contains(source))
|
||
|
return;
|
||
|
|
||
|
workingSet.add(source);
|
||
|
|
||
|
for (auto const& includeName : source->includes) {
|
||
|
if (auto include = namedSources.ptr(includeName))
|
||
|
dependencySortVisit(*include);
|
||
|
}
|
||
|
|
||
|
for (auto const& requirementName : source->requires) {
|
||
|
if (auto requirement = namedSources.ptr(requirementName))
|
||
|
dependencySortVisit(*requirement);
|
||
|
else
|
||
|
throw StarException(strf("Asset source '%s' is missing dependency '%s'", *source->name, requirementName));
|
||
|
}
|
||
|
|
||
|
workingSet.remove(source);
|
||
|
|
||
|
dependencySortedSources.add(move(source));
|
||
|
};
|
||
|
|
||
|
for (auto source : assetSources)
|
||
|
dependencySortVisit(move(source));
|
||
|
|
||
|
StringList sourcePaths;
|
||
|
for (auto const& source : dependencySortedSources) {
|
||
|
if (source->name)
|
||
|
Logger::info("Root: Detected asset source named '%s' at '%s'", *source->name, source->path);
|
||
|
else
|
||
|
Logger::info("Root: Detected unnamed asset source at '%s'", source->path);
|
||
|
sourcePaths.append(source->path);
|
||
|
}
|
||
|
|
||
|
return sourcePaths;
|
||
|
}
|
||
|
|
||
|
void Root::writeConfig() {
|
||
|
if (m_configuration) {
|
||
|
auto currentConfig = m_configuration->currentConfiguration();
|
||
|
if (m_lastRuntimeConfig != currentConfig) {
|
||
|
if (m_runtimeConfigFile) {
|
||
|
Logger::info("Root: Writing runtime configuration to '%s'", *m_runtimeConfigFile);
|
||
|
File::overwriteFileWithRename(currentConfig.printJson(2, true), *m_runtimeConfigFile);
|
||
|
}
|
||
|
m_lastRuntimeConfig = currentConfig;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename T, typename... Params>
|
||
|
shared_ptr<T> Root::loadMember(shared_ptr<T>& ptr, Mutex& mutex, char const* name, Params&&... params) {
|
||
|
return loadMemberFunction<T>(ptr, mutex, name, [&]() {
|
||
|
return make_shared<T>(forward<Params>(params)...);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
template <typename T>
|
||
|
shared_ptr<T> Root::loadMemberFunction(shared_ptr<T>& ptr, Mutex& mutex, char const* name, function<shared_ptr<T>()> loadFunction) {
|
||
|
MutexLocker locker(mutex);
|
||
|
if (!ptr) {
|
||
|
auto startSeconds = Time::monotonicTime();
|
||
|
ptr = loadFunction();
|
||
|
Logger::info("Root: Loaded %s in %s seconds", name, Time::monotonicTime() - startSeconds);
|
||
|
}
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
}
|