Code/StateManagement/InitialisationController.cs
using Sandbox;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SandbankDatabase;
sealed class InitialisationController : GameObjectSystem, ISceneStartup
{
public static object DatabaseStateLock = new();
public static DatabaseState CurrentDatabaseState;
/// <summary>
/// Prevents multiple threads trying to initialise/shut down the database at the same time.
/// </summary>
public static object InitialisationLock = new();
public InitialisationController( Scene scene ) : base( scene ) { }
void ISceneStartup.OnHostInitialize()
{
LoadHost();
}
void ISceneStartup.OnClientInitialize()
{
if ( Networking.IsClient && ConfigController.CLIENTS_CAN_USE )
LoadClient();
}
private void LoadHost()
{
ShutdownController.ShutdownHasBegun = false;
Initialise();
}
private void LoadClient()
{
ShutdownController.ShutdownHasBegun = false;
Initialise();
}
public static void Initialise()
{
if ( ShutdownController.ShutdownHasBegun )
return;
// The ticker, which runs in a background thread, is responsible for setting the database back to
// an uninitialised state. If it's still shutting down for some reason, we should wait until it's
// finished.
while ( InitialisationController.CurrentDatabaseState != DatabaseState.Uninitialised )
{
if ( InitialisationController.CurrentDatabaseState == DatabaseState.Initialised )
return;
Task.Delay( 10 ).GetAwaiter().GetResult();
}
lock ( InitialisationLock )
{
try
{
FileController.Initialise();
ConfigController.CreateConfigFileIfNone();
ConfigController.LoadConfigFile();
if ( !ConfigController.MERGE_JSON )
Logging.ScaryWarn( "Config.MERGE_JSON is set to false - this will delete data if you rename or remove a data field" );
if ( ConfigController.STARTUP_SHUTDOWN_MESSAGES )
{
Log.Info( "==================================" );
Log.Info( "Initialising Sandbank..." );
}
ShutdownController.WipeStaticFields();
FileController.EnsureFileSystemSetup();
LoadCollections();
lock ( DatabaseStateLock )
{
// Must set this before starting the ticker because the ticker kills itself when the database
// is no longer initialised.
CurrentDatabaseState = DatabaseState.Initialised;
}
Ticker.Initialise();
if ( ConfigController.STARTUP_SHUTDOWN_MESSAGES )
{
Log.Info( "Sandbank initialisation finished successfully" );
Log.Info( "==================================" );
}
}
catch ( Exception e )
{
Logging.Error( $"failed to initialise database: {Logging.ExtractExceptionString( e )}" );
if ( ConfigController.STARTUP_SHUTDOWN_MESSAGES )
{
Log.Info( "Sandbank initialisation finished unsuccessfully" );
Log.Info( "==================================" );
}
}
}
}
private static void LoadCollections()
{
int attempt = 0;
string error = null;
List<string> collectionNames;
while ( true )
{
if ( attempt++ >= 10 )
throw new SandbankException( $"failed to load collection list after 10 tries: {error}" );
(collectionNames, error) = FileController.ListCollectionNames();
if (error == null)
break;
}
attempt = 0;
foreach ( var collectionName in collectionNames )
{
Logging.Log( $"attempting to load collection \"{collectionName}\"" );
while ( true )
{
if ( attempt++ >= 10 )
throw new SandbankException( $"failed to load collection {collectionName} after 10 tries: {error}");
error = LoadCollection( collectionName );
if ( error == null )
break;
}
}
}
/// <summary>
/// Returns null on success or the error message on failure.
/// </summary>
private static string LoadCollection(string name)
{
var (definition, error) = FileController.LoadCollectionDefinition( name );
if ( error != null )
return $"failed loading collection definition for collection \"{name}\": {error}";
if (definition == null)
return $"found a folder for collection {name} but the definition.txt was missing in that folder or failed to load";
(var documents, error) = FileController.LoadAllCollectionsDocuments( definition );
if ( error != null )
return $"failed loading documents for collection \"{name}\": {error}";
Cache.CreateCollection( name, definition.DocumentClassType );
Cache.InsertDocumentsIntoCollection( name, documents );
Log.Info( $"Loaded collection {name} with {documents.Count} documents" );
return null;
}
}
internal enum DatabaseState
{
Uninitialised,
Initialised,
ShuttingDown
}