diff --git a/Ajuna.AspNetCore/Extensions/AjunaRuntime.cs b/Ajuna.AspNetCore/Extensions/AjunaRuntime.cs index be65909..8ee85b5 100644 --- a/Ajuna.AspNetCore/Extensions/AjunaRuntime.cs +++ b/Ajuna.AspNetCore/Extensions/AjunaRuntime.cs @@ -4,6 +4,6 @@ namespace Ajuna.AspNetCore.Extensions { internal static class AjunaRuntime { - internal static AjunaSubstrateService GameService { get; set; } + internal static AjunaSubstrateService AjunaSubstrateService { get; set; } } } \ No newline at end of file diff --git a/Ajuna.AspNetCore/Extensions/ServiceCollectionExtensions.cs b/Ajuna.AspNetCore/Extensions/ServiceCollectionExtensions.cs index f4c3170..1949602 100644 --- a/Ajuna.AspNetCore/Extensions/ServiceCollectionExtensions.cs +++ b/Ajuna.AspNetCore/Extensions/ServiceCollectionExtensions.cs @@ -10,22 +10,26 @@ public static class ServiceCollectionExtension { public static IServiceCollection AddAjunaStorageService(this IServiceCollection services, AjunaStorageServiceConfiguration configuration) { - var game = new AjunaSubstrateService(); + var ajunaSubstrateService = new AjunaSubstrateService(); Task.Run(async () => { // Initialize the storage service layer.. - await game.InitializeAsync(configuration); + // 1. A connection to the server is established + // 2. Subscribe to all Storage Changes + // 3. Gather all storage info from metadata and laod all Storage specific Delegates + // 4. Start Processing Changes + await ajunaSubstrateService.InitializeAsync(configuration); // Save the reference for later use. - AjunaRuntime.GameService = game; + AjunaRuntime.AjunaSubstrateService = ajunaSubstrateService; #pragma warning disable VSTHRD002 }).Wait(); #pragma warning restore VSTHRD002 - if (AjunaRuntime.GameService == null) + if (AjunaRuntime.AjunaSubstrateService == null) { throw new Exception("Could not initialize game service runtime. Please confirm that your configuration is correct."); } diff --git a/Ajuna.AspNetCore/StorageSubscriptionHandler.cs b/Ajuna.AspNetCore/StorageSubscriptionHandler.cs index 6a8e99c..b717e10 100644 --- a/Ajuna.AspNetCore/StorageSubscriptionHandler.cs +++ b/Ajuna.AspNetCore/StorageSubscriptionHandler.cs @@ -20,6 +20,13 @@ public StorageSubscriptionHandler(SubscriptionManager subscriptionManager) : bas { } + /// + /// Called on every Websocket call coming from the client + /// + /// + /// + /// + /// public override async Task ReceiveDelegateAsync(WebSocket socket, string socketId, WebSocketReceiveResult result, byte[] buffer) { if (!result.EndOfMessage || buffer == null || buffer.Length == 0) diff --git a/Ajuna.ServiceLayer/AjunaStorageServiceConfiguration.cs b/Ajuna.ServiceLayer/AjunaStorageServiceConfiguration.cs index dd8ba68..c9b0449 100644 --- a/Ajuna.ServiceLayer/AjunaStorageServiceConfiguration.cs +++ b/Ajuna.ServiceLayer/AjunaStorageServiceConfiguration.cs @@ -11,5 +11,10 @@ public class AjunaStorageServiceConfiguration public IStorageDataProvider DataProvider { get; set; } public List Storages { get; set; } + + /// + /// If true, the Service Layer will not fetch all initial Storage Values on Startup + /// + public bool IsLazyLoadingEnabled { get; set; } } } diff --git a/Ajuna.ServiceLayer/AjunaSubstrateService.cs b/Ajuna.ServiceLayer/AjunaSubstrateService.cs index 5662390..d1d2481 100644 --- a/Ajuna.ServiceLayer/AjunaSubstrateService.cs +++ b/Ajuna.ServiceLayer/AjunaSubstrateService.cs @@ -6,33 +6,37 @@ namespace Ajuna.ServiceLayer { public class AjunaSubstrateService { - private readonly AjunaSubstrateStorage GameStorage = new AjunaSubstrateStorage(); + private readonly AjunaSubstrateStorage _ajunaSubstrateStorage = new AjunaSubstrateStorage(); + /// + /// 1. A connection to the server is established + /// 2. Subscribe to all Storage Changes + /// 3. Gather all storage info from metadata and laod all Storage specific Delegates + /// 4. Start Processing Changes + /// + /// public async Task InitializeAsync(AjunaStorageServiceConfiguration configuration) { Log.Information("initialize Ajuna substrate service"); - // // Initialize substrate client API - // await configuration.DataProvider.ConnectAsync(configuration.CancellationToken); - // // Initialize storage systems // Start by subscribing to any storage change and then start loading // all storages that this service is interested in. - // + // While we are loading storages any storage subscription notification will - // wait to be processed until the initialization is complete. - await configuration.DataProvider.SubscribeStorageAsync(GameStorage.OnStorageUpdate); + // wait to be processed after the initialization is complete. + await configuration.DataProvider.SubscribeStorageAsync(_ajunaSubstrateStorage.OnStorageUpdate); - // Load storages we are interested in. - await GameStorage.InitializeAsync(configuration.DataProvider, configuration.Storages); + // Load storages we are interested in and register all Storage specific Delegates + await _ajunaSubstrateStorage.InitializeAsync(configuration.DataProvider, configuration.Storages, configuration.IsLazyLoadingEnabled); // Start processing subscriptions. - GameStorage.StartProcessingChanges(); + _ajunaSubstrateStorage.StartProcessingChanges(); } - public IStorage GetStorage() => GameStorage.GetStorage(); + public IStorage GetStorage() => _ajunaSubstrateStorage.GetStorage(); } } diff --git a/Ajuna.ServiceLayer/AjunaSubstrateStorage.cs b/Ajuna.ServiceLayer/AjunaSubstrateStorage.cs index 542e0f9..540c31e 100644 --- a/Ajuna.ServiceLayer/AjunaSubstrateStorage.cs +++ b/Ajuna.ServiceLayer/AjunaSubstrateStorage.cs @@ -17,10 +17,11 @@ internal class AjunaSubstrateStorage { private readonly ManualResetEvent StorageStartProcessingEvent = new ManualResetEvent(false); private readonly object Lock = new object(); - + + // Dictionary holding the info for each Storage. The key here is the Storage Key in its HEX encoded form. private readonly Dictionary StorageModuleItemInfos = new Dictionary(); - - private readonly Dictionary> StorageChangeListener = new Dictionary>(); + + private readonly Dictionary> StorageChangeDelegates = new Dictionary>(); private List Storages = new List(); @@ -42,20 +43,37 @@ internal IStorage GetStorage() throw new KeyNotFoundException($"Could not find storage {typeof(T).Name} in storage list."); } - - internal async Task InitializeAsync(IStorageDataProvider dataProvider, List storages) + + /// + /// Gather all storage info from metadata and laod all Storage specific Delegates + /// + /// + /// + /// /// + internal async Task InitializeAsync(IStorageDataProvider dataProvider, List storages, bool isLazyLoadingEnabled) { Storages = storages; + // Gather all Storage Info from the Metadata InitializeMetadataDisplayNames(dataProvider.GetMetadata()); + + // Register all Storage specific Delegates InitializeStorageChangeListener(); - foreach (IStorage storage in Storages) + + if (!isLazyLoadingEnabled) { - await storage.InitializeAsync(dataProvider); + // Initializes Storages fetching all their initial Values + foreach (IStorage storage in Storages) + { + await storage.InitializeAsync(dataProvider); + } } } + /// + /// Registers the Listener for all Storages + /// private void InitializeStorageChangeListener() { foreach (IStorage storage in Storages) @@ -66,14 +84,19 @@ private void InitializeStorageChangeListener() foreach (object attribute in attributes) { var listenerMethod = attribute as StorageChangeAttribute; - StorageChangeListener.Add(listenerMethod.Key, new Tuple(storage, method)); + StorageChangeDelegates.Add(listenerMethod.Key, new Tuple(storage, method)); } } } } + /// + /// Gathers all Storage Info from the Metadata + /// + /// private void InitializeMetadataDisplayNames(MetaData metadata) { + // Iterate through all pallets foreach (PalletModule palletModule in metadata.NodeMetadata.Modules.Values) { string moduleName = palletModule.Name; @@ -83,6 +106,8 @@ private void InitializeMetadataDisplayNames(MetaData metadata) continue; } + // For each storage that you find, get the Pallet/Module and Storage Name and add them to the + // Dictionary that has the Storage Hex key as a key foreach (Entry storage in palletModule.Storage.Entries) { var itemInfo = new ItemInfo @@ -108,6 +133,12 @@ private void InitializeMetadataDisplayNames(MetaData metadata) Log.Information("loaded storage metadata modules {count}", StorageModuleItemInfos.Count); } + + /// + /// Handles the incoming Storage Change + /// + /// + /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "May be used later.")] internal void OnStorageUpdate(string id, StorageChangeSet changes) { @@ -156,13 +187,21 @@ internal void StartProcessingChanges() StorageStartProcessingEvent.Set(); } + /// + /// Looks in the StorageChangeListeners to see + /// if a listener is already registered for the incoming change and triggers it. + /// + /// + /// + /// + /// private void ProcessStorageChange(ItemInfo itemInfo, string[] storageItemKeys, string data) { string key = $"{itemInfo.ModuleName}.{itemInfo.StorageName}"; - if (StorageChangeListener.ContainsKey(key)) + if (StorageChangeDelegates.ContainsKey(key)) { - Tuple listener = StorageChangeListener[key]; + Tuple listener = StorageChangeDelegates[key]; string[] parameters = new string[storageItemKeys.Length + 1]; parameters[parameters.Length - 1] = data; diff --git a/Ajuna.ServiceLayer/Storage/IStorageDataProvider.cs b/Ajuna.ServiceLayer/Storage/IStorageDataProvider.cs index 2772667..94b72b1 100644 --- a/Ajuna.ServiceLayer/Storage/IStorageDataProvider.cs +++ b/Ajuna.ServiceLayer/Storage/IStorageDataProvider.cs @@ -8,12 +8,21 @@ namespace Ajuna.ServiceLayer.Storage { + /// + /// Provides data fetching and subscription functionality for the node + /// public interface IStorageDataProvider { Task> GetStorageDictAsync(string module, string storageName) where T : IType, new(); Task GetStorageAsync(string module, string storageName) where T : IType, new(); MetaData GetMetadata(); Task ConnectAsync(CancellationToken cancellationToken); + + /// + /// Subscribes to all storage changes + /// + /// Delegate to be executed on every storage change + /// Task SubscribeStorageAsync(Action onStorageUpdate); void BroadcastLocalStorageChange(string id, StorageChangeSet changeSet); } diff --git a/Ajuna.ServiceLayer/Storage/TypedMapStorage.cs b/Ajuna.ServiceLayer/Storage/TypedMapStorage.cs index ee4f1d0..c14bc10 100644 --- a/Ajuna.ServiceLayer/Storage/TypedMapStorage.cs +++ b/Ajuna.ServiceLayer/Storage/TypedMapStorage.cs @@ -27,6 +27,7 @@ public TypedMapStorage(string identifier, IStorageDataProvider dataProvider, Lis Identifier = identifier; DataProvider = dataProvider; ChangeDelegates = changeDelegates; + Dictionary = new Dictionary(); } public async Task InitializeAsync(string module, string moduleItem) diff --git a/Ajuna.ServiceLayer/Storage/TypedStorage.cs b/Ajuna.ServiceLayer/Storage/TypedStorage.cs index b42eab3..c02ca7c 100644 --- a/Ajuna.ServiceLayer/Storage/TypedStorage.cs +++ b/Ajuna.ServiceLayer/Storage/TypedStorage.cs @@ -27,6 +27,7 @@ public TypedStorage(string identifier, IStorageDataProvider dataProvider, List