public SuperTileGenerator(IConfigSource config, RDBMap rdbmap) { var tileInfo = config.Configs["MapTileInfo"]; _tilePixelSize = tileInfo?.GetInt("PixelScale", Constants.PixelScale) ?? Constants.PixelScale; var zoomInfo = config.Configs["TileZooming"]; _maxZoomLevel = zoomInfo?.GetInt("HighestZoomLevel", Constants.HighestZoomLevel) ?? Constants.HighestZoomLevel; _imageWriter = new TileImageWriter(config); _rdbMap = rdbmap; var _tileInfo = config.Configs["MapTileInfo"]; _oceanColor = Color.FromArgb( _tileInfo?.GetInt("OceanColorRed", Constants.OceanColor.R) ?? Constants.OceanColor.R, _tileInfo?.GetInt("OceanColorGreen", Constants.OceanColor.G) ?? Constants.OceanColor.G, _tileInfo?.GetInt("OceanColorBlue", Constants.OceanColor.B) ?? Constants.OceanColor.B ); _serverMode = config.Configs["Startup"].GetBoolean("ServerMode", Constants.KeepRunningDefault); }
public static int Main(string[] args) { // First line, hook the appdomain to the crash reporter #pragma warning disable RECS0164 // Explicit delegate creation expression is redundant // Analysis disable once RedundantDelegateCreation // The "new" is required. AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); #pragma warning restore RECS0164 // Explicit delegate creation expression is redundant var watch = System.Diagnostics.Stopwatch.StartNew(); // Add the arguments supplied when running the application to the configuration var configSource = new ArgvConfigSource(args); _configSource = configSource; // Commandline switches configSource.AddSwitch("Startup", "inifile"); configSource.AddSwitch("Startup", "logconfig"); configSource.AddSwitch("Startup", "MaxParallelism", "p"); configSource.AddSwitch("Startup", "ServerMode"); var startupConfig = _configSource.Configs["Startup"]; // TODO: var pidFileManager = new PIDFileManager(startupConfig.GetString("pidfile", string.Empty)); // Configure Log4Net { var logConfigFile = startupConfig.GetString("logconfig", string.Empty); if (string.IsNullOrEmpty(logConfigFile)) { XmlConfigurator.Configure(); LogBootMessage(); LOG.Info("Configured log4net using ./Anaximander.exe.config as the default."); } else { XmlConfigurator.Configure(new FileInfo(logConfigFile)); LogBootMessage(); LOG.Info($"Configured log4net using \"{logConfigFile}\" as configuration file."); } } // Configure nIni aliases and localles Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US", true); configSource.Alias.AddAlias("On", true); configSource.Alias.AddAlias("Off", false); configSource.Alias.AddAlias("True", true); configSource.Alias.AddAlias("False", false); configSource.Alias.AddAlias("Yes", true); configSource.Alias.AddAlias("No", false); // Read in the ini file ReadConfigurationFromINI(configSource); var configRead = configSource.Configs["AssetsRead"]; var serversRead = GetServers(configSource, configRead, _assetServersByName); var chattelConfigRead = GetConfig(configRead, serversRead); // Create an IPC wait handle with a unique identifier. var serverMode = startupConfig.GetBoolean("ServerMode", Constants.KeepRunningDefault); var createdNew = true; var waitHandle = serverMode ? new EventWaitHandle(false, EventResetMode.AutoReset, "4d1ede7a-7f81-4934-bc59-f4fe10396408", out createdNew) : null; var serverState = serverMode ? ServerState.Starting : ServerState.Ignored; // If the handle was already there, inform the user and die. if (serverState == ServerState.Starting && !createdNew) { LOG.Error("Server process alredy started, please stop that server first."); return(2); } LOG.Info($"Configured for max degree of parallelism of {startupConfig.GetInt("MaxParallelism", Constants.MaxDegreeParallism)}"); var readerLocalStorage = new AssetStorageSimpleFolderTree(chattelConfigRead); var chattelReader = new ChattelReader(chattelConfigRead, readerLocalStorage); // TODO: add purge flag to CLI Texture.Initialize(chattelReader); watch.Stop(); LOG.Info($"Read configuration in {watch.ElapsedMilliseconds} ms."); watch.Restart(); // Load the RDB map try { _rdbMap = new RDBMap(configSource); } catch (DatabaseException e) { LOG.Error($"Unable to continue without database connection. Aborting.", e); return(1); } watch.Stop(); LOG.Info($"Loaded region DB in {watch.ElapsedMilliseconds} ms for a total of {_rdbMap.GetRegionCount()} regions, resulting in an average of {(float)watch.ElapsedMilliseconds / _rdbMap.GetRegionCount()} ms / region."); watch.Restart(); /* Issues to watch for: * Region delete - The DBA will need to actually remove the estate record to cause a map tile delete. * TODO: Tile image read during write - The web server could attempt to read a file while the file is being written. * - Possible solution: write to a random filename then try { mv rndname to finalname with overwrite } catch { try again later for a max of N times } * This should provide as much atomicity as possible, and allow anything that's blocking access to be bypassed via time delay. Needs to just fail under exceptions that indicate always-fail conditions. */ { LOG.Debug("Initializing writer and generator."); _tileWriter = new TileImageWriter(configSource); _tileGenerator = new TileGenerator(configSource); LOG.Debug("Writing ocean tile."); // Generate & replace ocean tile using (var ocean_tile = _tileGenerator.GenerateOceanTile()) { _tileWriter.WriteOceanTile(ocean_tile.Bitmap); } LOG.Debug("Generating a full batch of region tiles."); // Generate region tiles - all existing are nearly guaranteed to be out of date. var options = new ParallelOptions { MaxDegreeOfParallelism = startupConfig.GetInt("MaxParallelism", Constants.MaxDegreeParallism) }; // -1 means full parallel. 1 means non-parallel. Parallel.ForEach(_rdbMap.GetRegionUUIDs(), options, (region_id) => { var oldPriority = Thread.CurrentThread.Priority; try { Thread.CurrentThread.Priority = ThreadPriority.BelowNormal; UpdateRegionTile(region_id); } finally { Thread.CurrentThread.Priority = oldPriority; } }); watch.Stop(); LOG.Info($"Created full res map tiles in {watch.ElapsedMilliseconds} ms all regions with known locations, resulting in an average of {(float)watch.ElapsedMilliseconds / _rdbMap.GetRegionCount()} ms / region."); watch.Restart(); // Generate zoom level tiles. // Just quickly build the tile tree so that lookups of the super tiles can be done. var superGen = new SuperTileGenerator(configSource, _rdbMap); superGen.PreloadTileTrees(_rdbMap.GetRegionUUIDs()); watch.Stop(); LOG.Info($"Preloaded tile tree in {watch.ElapsedMilliseconds} ms."); watch.Restart(); // Remove all tiles that do not have a corresponding entry in the map. _tileWriter.RemoveDeadTiles(_rdbMap, superGen.AllNodesById); watch.Stop(); LOG.Info($"Removed all old tiles in {watch.ElapsedMilliseconds} ms."); watch.Restart(); // Actually generate the zoom level tiles. superGen.GeneratePreloadedTree(); watch.Stop(); LOG.Info($"Created all super tiles in {watch.ElapsedMilliseconds} ms."); } // Activate server process if (serverState == ServerState.Starting) { System.Console.CancelKeyPress += (sender, cargs) => { cargs.Cancel = true; waitHandle.Set(); }; serverState = ServerState.Running; var server_config = configSource.Configs["Server"]; var domain = server_config?.GetString("UseSSL", Constants.ServerDomain) ?? Constants.ServerDomain; var port = (uint)(server_config?.GetInt("UseSSL", Constants.ServerPort) ?? Constants.ServerPort); var useSSL = server_config?.GetBoolean("UseSSL", Constants.ServerUseSSL) ?? Constants.ServerUseSSL; var protocol = useSSL ? "https" : "http"; LOG.Info($"Activating server on '{protocol}://{domain}:{port}', listening for region updates."); RestApi.RestAPI.StartHost( UpdateRegionDelegate, MapRulesDelegate, CheckAPIKeyDelegate, domain, port, useSSL ); waitHandle.WaitOne(); serverState = ServerState.Stopping; } // I don't care what's still connected or keeping things running, it's time to die! Environment.Exit(0); return(0); }