// Copyright © 2017-2018 Atomic Software, LLC. All Rights Reserved. // See LICENSE.md for full license information. using Atom.Core.Diagnostics; using Atom.Core.IO; using Atom.Core.Networking; using Atom.Core.Networking.Messages; using Atom.Core.Platform; using System; using System.Collections.Generic; using System.Data; using System.Data.Odbc; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime; using System.Threading; namespace Atom.Core { public class Server { public bool IsRunning { get; set; } public OdbcConnection ODBCConnection { get; set; } public ConfigurationFile Config { get; set; } public bool IsODBCConnected => ODBCConnection.State == ConnectionState.Open; private readonly Stopwatch StartTimer; private readonly string ConfigFileName = $"{AtomAssembly.Name}.ini"; private readonly object ExceptionLock = new object(); private bool IsHandlingException; private IEnumerable Engines; public Server() { StartTimer = new Stopwatch(); } public void Start() { AppDomain.CurrentDomain.UnhandledException += ReportCrash; Console.Title = $"{AtomAssembly.Name} v{AtomAssembly.Version} {(Debugger.IsAttached ? " (Debugging)" : "")}"; IsRunning = true; Config = new ConfigurationFile(AppDomain.CurrentDomain.BaseDirectory + ConfigFileName); Engines = new Collections.List(); StartTimer.Start(); if (!Environment.Is64BitProcess) { Log.PerformanceWarning("The process is currently not in 64-bit mode. Performance issues may occur. Press any key to continue with initialization."); Console.ReadKey(); } if (!GCSettings.IsServerGC) { Log.Warning("GC is not configured to server mode"); } Log.Info($"GC latency mode is set to '{GCSettings.LatencyMode}'"); Log.Info("Launching the server"); Log.Info($"Local Time: {DateTime.Now}"); Log.Info($"System Time: (UTC): {DateTime.UtcNow}"); Log.Info("Loading configuration"); if (!File.Exists(ConfigFileName)) { Log.CriticalError($"Could not find the file '{ConfigFileName}'. Press any key to stop the server."); Console.ReadKey(); Stop(); return; } Log.Info($"Loaded server configuration '{ConfigFileName}'"); Log.Info("Connect to the database"); ConnectToDatabase(); Log.Info("Loading message handlers"); MessageHandlerLoader.LoadHandlers(); PerformStartActions(); StartEngines(); StartMainLoop(); StartTimer.Stop(); Log.Info($"Start time: ({StartTimer.ElapsedMilliseconds})ms"); } private void ConnectToDatabase() { ODBCConnection = new OdbcConnection(Config.GetString("ODBC", "ConnectionString")); try { ODBCConnection.Open(); var StartupCommand = ODBCConnection.CreateCommand(); StartupCommand.CommandText = Config.GetString("ODBC", "StartupCommand"); StartupCommand.ExecuteNonQuery(); Log.Info($"Connected to the database '{ODBCConnection.DataSource}'"); } catch (Exception ex) { Log.Warning("Database connection attempt failed"); Log.Warning(ex.Message); Thread.Sleep(3000); } } public void Stop(Exception exception = null) { if (exception != null) { Log.CriticalError("CrashLog", $"{exception}", false); Log.CriticalError("The server has crashed. Check the crash log."); } Client.ActiveClients.ToList().ForEach(x => x.Disconnect()); Listener.ActiveListeners.ToList().ForEach(x => x.Shutdown()); PerformStopActions(); Log.Warning("The server is now offline. Press ESC to exit."); while (!Console.KeyAvailable || Console.ReadKey(true).Key != ConsoleKey.Escape) { } IsRunning = false; Environment.Exit(0); } private void StartEngines() { lock (Engines) { Engines = from Type in Assembly.GetEntryAssembly().GetTypes() where Type.GetInterfaces().Contains(typeof(IEngine)) && Type.GetConstructor(Type.EmptyTypes) != null select Activator.CreateInstance(Type) as IEngine; } } private void StartMainLoop() { new Thread(() => { while (IsRunning) { var CurrentTime = SystemTime.Milliseconds(); lock (Engines) { foreach (var Engine in Engines) { Engine.Main(CurrentTime); } } Main(CurrentTime); Thread.Sleep(10); } }).Start(); } /// /// is a method able to be overidden to perform /// additional actions once the server starts. It is called immediately after the /// generic start code is executed. If the method is not declared, then no additional /// code will be executed. /// protected virtual void PerformStartActions() { } /// /// is a method able to be overidden to perform /// additional actions once the server stops. It is called immediately after the /// generic stop code is executed. If the method is not declared, then no additional /// code will be executed. /// protected virtual void PerformStopActions() { } /// /// is a method able to be overidden to perform /// additional actions during the server's main loop. It is called immediately after the /// generic main loop code is executed. If the method is not declared, then no additional /// code will be executed. /// protected virtual void Main(long now) { } private void ReportCrash(object sender, UnhandledExceptionEventArgs e) { lock (ExceptionLock) { if (IsHandlingException) { return; } IsHandlingException = true; } Stop((Exception) e.ExceptionObject); } } }