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); }
private static void UpdateRegionDelegate(Guid uuid, RestApi.ChangeInfo changeData) { var watch = System.Diagnostics.Stopwatch.StartNew(); var redraw = false; if (_rdbMap.GetRegionByUUID(uuid) != null) { foreach (var change in changeData.Changes) { switch (change) { case RestApi.ChangeCategory.RegionStart: // Get all new data. //redraw = _rdbMap.CreateRegion(uuid); break; case RestApi.ChangeCategory.RegionStop: // RegionStop just means redraw but no need to update source data. redraw = true; break; case RestApi.ChangeCategory.TerrainElevation: case RestApi.ChangeCategory.TerrainTexture: //_rdbMap.UpdateRegionTerrainData(uuid); redraw = true; break; case RestApi.ChangeCategory.Prim: //_rdbMap.UpdateRegionPrimData(uuid); redraw = true; break; default: throw new InvalidOperationException($"Unexpected value change = {change}"); } } } else // New region, maybe. //redraw = _rdbMap.CreateRegion(uuid); { } if (redraw) { UpdateRegionTile(uuid); var superGen = new SuperTileGenerator(_configSource, _rdbMap); // Only update that portion of the tree that's affected by the change. superGen.PreloadTileTrees(new[] { uuid }); superGen.GeneratePreloadedTree(); // Time for cleanup: make sure that we only have what we need. superGen.PreloadTileTrees(_rdbMap.GetRegionUUIDs()); _tileWriter.RemoveDeadTiles(_rdbMap, superGen.AllNodesById); watch.Stop(); LOG.Info($"Rebuilt region id '{uuid}', parent tree, and did filesystem cleanup in {watch.ElapsedMilliseconds} ms."); } else { watch.Stop(); LOG.Info($"Got request to rebuild region id '{uuid}', but there was nothing to do."); } }