Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Ajuna.AspNetCore/Extensions/AjunaRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace Ajuna.AspNetCore.Extensions
{
internal static class AjunaRuntime
{
internal static AjunaSubstrateService GameService { get; set; }
internal static AjunaSubstrateService AjunaSubstrateService { get; set; }
}
}
12 changes: 8 additions & 4 deletions Ajuna.AspNetCore/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
Expand Down
7 changes: 7 additions & 0 deletions Ajuna.AspNetCore/StorageSubscriptionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ public StorageSubscriptionHandler(SubscriptionManager subscriptionManager) : bas
{
}

/// <summary>
/// Called on every Websocket call coming from the client
/// </summary>
/// <param name="socket"></param>
/// <param name="socketId"></param>
/// <param name="result"></param>
/// <param name="buffer"></param>
public override async Task ReceiveDelegateAsync(WebSocket socket, string socketId, WebSocketReceiveResult result, byte[] buffer)
{
if (!result.EndOfMessage || buffer == null || buffer.Length == 0)
Expand Down
5 changes: 5 additions & 0 deletions Ajuna.ServiceLayer/AjunaStorageServiceConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ public class AjunaStorageServiceConfiguration
public IStorageDataProvider DataProvider { get; set; }

public List<IStorage> Storages { get; set; }

/// <summary>
/// If true, the Service Layer will not fetch all initial Storage Values on Startup
/// </summary>
public bool IsLazyLoadingEnabled { get; set; }
}
}
26 changes: 15 additions & 11 deletions Ajuna.ServiceLayer/AjunaSubstrateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,37 @@ namespace Ajuna.ServiceLayer
{
public class AjunaSubstrateService
{
private readonly AjunaSubstrateStorage GameStorage = new AjunaSubstrateStorage();
private readonly AjunaSubstrateStorage _ajunaSubstrateStorage = new AjunaSubstrateStorage();

/// <summary>
/// 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
/// </summary>
/// <param name="configuration"></param>
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<T>() => GameStorage.GetStorage<T>();
public IStorage GetStorage<T>() => _ajunaSubstrateStorage.GetStorage<T>();
}
}
59 changes: 49 additions & 10 deletions Ajuna.ServiceLayer/AjunaSubstrateStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ItemInfo> StorageModuleItemInfos = new Dictionary<string, ItemInfo>();

private readonly Dictionary<string, Tuple<object, MethodInfo>> StorageChangeListener = new Dictionary<string, Tuple<object, MethodInfo>>();
private readonly Dictionary<string, Tuple<object, MethodInfo>> StorageChangeDelegates = new Dictionary<string, Tuple<object, MethodInfo>>();

private List<IStorage> Storages = new List<IStorage>();

Expand All @@ -42,20 +43,37 @@ internal IStorage GetStorage<T>()

throw new KeyNotFoundException($"Could not find storage {typeof(T).Name} in storage list.");
}

internal async Task InitializeAsync(IStorageDataProvider dataProvider, List<IStorage> storages)

/// <summary>
/// Gather all storage info from metadata and laod all Storage specific Delegates
/// </summary>
/// <param name="dataProvider"></param>
/// <param name="storages"></param>
/// /// <param name="initialzeStorages"></param>
internal async Task InitializeAsync(IStorageDataProvider dataProvider, List<IStorage> 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);
}
}
}

/// <summary>
/// Registers the Listener for all Storages
/// </summary>
private void InitializeStorageChangeListener()
{
foreach (IStorage storage in Storages)
Expand All @@ -66,14 +84,19 @@ private void InitializeStorageChangeListener()
foreach (object attribute in attributes)
{
var listenerMethod = attribute as StorageChangeAttribute;
StorageChangeListener.Add(listenerMethod.Key, new Tuple<object, MethodInfo>(storage, method));
StorageChangeDelegates.Add(listenerMethod.Key, new Tuple<object, MethodInfo>(storage, method));
}
}
}
}

/// <summary>
/// Gathers all Storage Info from the Metadata
/// </summary>
/// <param name="metadata"></param>
private void InitializeMetadataDisplayNames(MetaData metadata)
{
// Iterate through all pallets
foreach (PalletModule palletModule in metadata.NodeMetadata.Modules.Values)
{
string moduleName = palletModule.Name;
Expand All @@ -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
Expand All @@ -108,6 +133,12 @@ private void InitializeMetadataDisplayNames(MetaData metadata)
Log.Information("loaded storage metadata modules {count}", StorageModuleItemInfos.Count);
}


/// <summary>
/// Handles the incoming Storage Change
/// </summary>
/// <param name="id"></param>
/// <param name="changes"></param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "May be used later.")]
internal void OnStorageUpdate(string id, StorageChangeSet changes)
{
Expand Down Expand Up @@ -156,13 +187,21 @@ internal void StartProcessingChanges()
StorageStartProcessingEvent.Set();
}

/// <summary>
/// Looks in the StorageChangeListeners to see
/// if a listener is already registered for the incoming change and triggers it.
/// </summary>
/// <param name="itemInfo"></param>
/// <param name="storageItemKeys"></param>
/// <param name="data"></param>
/// <exception cref="NotImplementedException"></exception>
private void ProcessStorageChange(ItemInfo itemInfo, string[] storageItemKeys, string data)
{
string key = $"{itemInfo.ModuleName}.{itemInfo.StorageName}";

if (StorageChangeListener.ContainsKey(key))
if (StorageChangeDelegates.ContainsKey(key))
{
Tuple<object, MethodInfo> listener = StorageChangeListener[key];
Tuple<object, MethodInfo> listener = StorageChangeDelegates[key];

string[] parameters = new string[storageItemKeys.Length + 1];
parameters[parameters.Length - 1] = data;
Expand Down
9 changes: 9 additions & 0 deletions Ajuna.ServiceLayer/Storage/IStorageDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@

namespace Ajuna.ServiceLayer.Storage
{
/// <summary>
/// Provides data fetching and subscription functionality for the node
/// </summary>
public interface IStorageDataProvider
{
Task<Dictionary<string, T>> GetStorageDictAsync<T>(string module, string storageName) where T : IType, new();
Task<T> GetStorageAsync<T>(string module, string storageName) where T : IType, new();
MetaData GetMetadata();
Task ConnectAsync(CancellationToken cancellationToken);

/// <summary>
/// Subscribes to all storage changes
/// </summary>
/// <param name="onStorageUpdate">Delegate to be executed on every storage change</param>
/// <returns></returns>
Task SubscribeStorageAsync(Action<string, StorageChangeSet> onStorageUpdate);
void BroadcastLocalStorageChange(string id, StorageChangeSet changeSet);
}
Expand Down
1 change: 1 addition & 0 deletions Ajuna.ServiceLayer/Storage/TypedMapStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public TypedMapStorage(string identifier, IStorageDataProvider dataProvider, Lis
Identifier = identifier;
DataProvider = dataProvider;
ChangeDelegates = changeDelegates;
Dictionary = new Dictionary<string, T>();
}

public async Task InitializeAsync(string module, string moduleItem)
Expand Down
1 change: 1 addition & 0 deletions Ajuna.ServiceLayer/Storage/TypedStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public TypedStorage(string identifier, IStorageDataProvider dataProvider, List<I
Identifier = identifier;
DataProvider = dataProvider;
ChangeDelegates = changeDelegates;
Store = new T();
}

public async Task InitializeAsync(string module, string moduleItem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ public void ConfigureServices(IServiceCollection services)
{
CancellationToken = CTS.Token,
DataProvider = _storageDataProvider,
Storages = GetRuntimeStorages()
Storages = GetRuntimeStorages(),
IsLazyLoadingEnabled = false // Set to true if you prefer to avoid loading all initial Storage values at the service startup
});

// Register data provider as singleton.
Expand Down