// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Horde;
using HordeServer.Server;
using MongoDB.Bson.Serialization.Attributes;
namespace HordeServer.Utilities
{
///
/// Identifier for a pool
///
/// Id to construct from
[StringIdConverter(typeof(SingletonIdConverter))]
public record struct SingletonId(StringId Id)
{
///
/// Constructor
///
public SingletonId(string id) : this(new StringId(id)) { }
///
public override string ToString() => Id.ToString();
}
///
/// Converter to and from instances.
///
class SingletonIdConverter : StringIdConverter
{
///
public override SingletonId FromStringId(StringId id) => new SingletonId(id);
///
public override StringId ToStringId(SingletonId value) => value.Id;
}
///
/// Base class for singleton documents
///
public abstract class SingletonBase
{
///
/// Unique id for this singleton
///
[BsonId]
public SingletonId Id { get; set; }
///
/// The revision index of this document
///
public int Revision { get; set; }
///
/// Callback to allow the singleton to fix up itself after being read
///
public virtual void PostLoad()
{
}
}
///
/// Attribute specifying the unique id for a singleton document
///
[AttributeUsage(AttributeTargets.Class)]
public sealed class SingletonDocumentAttribute : Attribute
{
///
/// Unique id for the singleton document
///
public string Id { get; }
///
/// ObjectId for the legacy document
///
public string? LegacyId { get; }
///
/// Constructor
///
/// Unique id for the singleton document. Should be a valid StringId.
/// Legacy identifier for the document
public SingletonDocumentAttribute(string id, string? legacyId = null)
{
Id = id;
LegacyId = legacyId;
}
}
///
/// Interface for the getting and setting the singleton
///
/// Type of document
public interface ISingletonDocument
{
///
/// Gets the current document
///
/// Cancellation token for the operation
/// The current document
Task GetAsync(CancellationToken cancellationToken);
///
/// Attempts to update the document
///
/// New state of the document
/// Cancellation token for the operation
/// True if the document was updated, false otherwise
Task TryUpdateAsync(T value, CancellationToken cancellationToken);
}
///
/// Concrete implementation of
///
/// The document type
public class SingletonDocument : ISingletonDocument where T : SingletonBase, new()
{
///
/// The database service instance
///
readonly IMongoService _mongoService;
///
/// Constructor
///
/// The database service instance
public SingletonDocument(IMongoService mongoService)
{
_mongoService = mongoService;
}
///
public async Task GetAsync(CancellationToken cancellationToken)
{
return await _mongoService.GetSingletonAsync(cancellationToken);
}
///
public Task TryUpdateAsync(T value, CancellationToken cancellationToken)
{
return _mongoService.TryUpdateSingletonAsync(value, cancellationToken);
}
}
///
/// Extension methods for singletons
///
public static class SingletonDocumentExtensions
{
///
/// Update a singleton
///
///
///
///
/// Cancellation token for the operation
///
public static async Task UpdateAsync(this ISingletonDocument singleton, Action updateAction, CancellationToken cancellationToken) where T : SingletonBase, new()
{
for (; ; )
{
T value = await singleton.GetAsync(cancellationToken);
updateAction(value);
if (await singleton.TryUpdateAsync(value, cancellationToken))
{
return value;
}
}
}
}
}