Skip to content

Commit

Permalink
Fix message bus not handling base classes and interfaces, rename defa…
Browse files Browse the repository at this point in the history
…ult implementations of some systems, add more tests
  • Loading branch information
Edvinas01 committed Sep 8, 2024
1 parent dd0b5b6 commit 693f323
Show file tree
Hide file tree
Showing 20 changed files with 406 additions and 119 deletions.
2 changes: 2 additions & 0 deletions Packages/com.chark.game-management/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixed

- `GameStorage.GetValueAsync` (now `GameStorage.ReadValueAsync`) not switching back to main thread when no value is found.
- Message bus not handling abstract and interface listeners.
- Message bus iteration breaking when listener would remove itself during `Raise` call.

## [v0.0.2](https://github.com/chark/game-management/compare/v0.0.1...v0.0.2) - 2023-10-06

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@

namespace CHARK.GameManagement.Assets
{
internal sealed class ResourceLoader : IResourceLoader
internal sealed class DefaultResourceLoader : IResourceLoader
{
private readonly ISerializer serializer;

public ResourceLoader(ISerializer serializer)
public DefaultResourceLoader(ISerializer serializer)
{
this.serializer = serializer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

namespace CHARK.GameManagement.Entities
{
internal sealed class EntityManager : IEntityManager
internal sealed class DefaultEntityManager : IEntityManager
{
private readonly List<object> entities = new();
private readonly IGameManagerSettingsProfile profile;

public IReadOnlyList<object> Entities => entities;

public EntityManager(IGameManagerSettingsProfile profile)
public DefaultEntityManager(IGameManagerSettingsProfile profile)
{
this.profile = profile;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,7 @@ public static void Publish<TMessage>(TMessage message) where TMessage : IMessage
}

/// <inheritdoc cref="IMessageBus.AddListener{TMessage}"/>
public static void AddListener<TMessage>(Action<TMessage> listener)
where TMessage : IMessage
public static void AddListener<TMessage>(OnMessageReceived<TMessage> listener) where TMessage : IMessage
{
var gameManager = GetGameManager();
var messageBus = gameManager.messageBus;
Expand All @@ -251,8 +250,7 @@ public static void AddListener<TMessage>(Action<TMessage> listener)
}

/// <inheritdoc cref="IMessageBus.RemoveListener{TMessage}"/>
public static void RemoveListener<TMessage>(Action<TMessage> listener)
where TMessage : IMessage
public static void RemoveListener<TMessage>(OnMessageReceived<TMessage> listener) where TMessage : IMessage
{
var gameManager = GetGameManager();
var messageBus = gameManager.messageBus;
Expand Down
8 changes: 4 additions & 4 deletions Packages/com.chark.game-management/Runtime/GameManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ protected virtual ISerializer CreateSerializer()
/// </returns>
protected virtual IStorage CreateRuntimeStorage()
{
return new FileStorage(
return new DefaultFileStorage(
serializer: serializer,
profile: Settings.ActiveProfile,
persistentDataPath: Application.persistentDataPath,
Expand All @@ -172,7 +172,7 @@ protected virtual IStorage CreateRuntimeStorage()
/// </returns>
protected virtual IResourceLoader CreateResourceLoader()
{
return new ResourceLoader(serializer);
return new DefaultResourceLoader(serializer);
}

/// <returns>
Expand All @@ -181,15 +181,15 @@ protected virtual IResourceLoader CreateResourceLoader()
protected virtual IEntityManager CreateEntityManager()
{
var profile = Settings.ActiveProfile;
return new EntityManager(profile);
return new DefaultEntityManager(profile);
}

/// <returns>
/// New <see cref="IMessageBus"/> instance.
/// </returns>
protected virtual IMessageBus CreateMessageBus()
{
return new MessageBus();
return new DefaultMessageBus();
}

private void InitializeGameManager()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using CHARK.GameManagement.Utilities;

namespace CHARK.GameManagement.Messaging
{
internal sealed class DefaultMessageBus : IMessageBus
{
private readonly IDictionary<Type, Type[]> interfacesByListenerTypeCache =
new Dictionary<Type, Type[]>();

private readonly IDictionary<Type, MessageListener> listenersByType =
new Dictionary<Type, MessageListener>();

public int CachedTypeCount => interfacesByListenerTypeCache.Count;

public int MessageListenerCount => listenersByType.Count;

public int TotalListenerCount
{
get
{
var totalListenerCount = 0;
foreach (var messageListener in listenersByType.Values)
{
totalListenerCount += messageListener.ListenerCount;
}

return totalListenerCount;
}
}

public void Publish<TMessage>(TMessage message) where TMessage : IMessage
{
#if UNITY_EDITOR
if (message == null)
{
Logging.LogError($"Message of type {typeof(TMessage)} cannot be null", GetType());
return;
}
#endif

if (TryGetListener<TMessage>(out var messageListener) == false)
{
#if UNITY_EDITOR
Logging.LogWarning($"Could not find a listener for {typeof(TMessage)}", GetType());
#endif
return;
}

messageListener.Raise(message);
}

public void AddListener<TMessage>(OnMessageReceived<TMessage> listener) where TMessage : IMessage
{
#if UNITY_EDITOR
if (listener == null)
{
Logging.LogError($"Listener of type {typeof(TMessage)} cannot be null", GetType());
return;
}
#endif

var listenerType = typeof(TMessage);
if (TryGetListener<TMessage>(out var messageListener))
{
messageListener.AddListener(listener);
return;
}

var newMessageListener = new MessageListener();
newMessageListener.AddListener(listener);

listenersByType[listenerType] = newMessageListener;
}

public void RemoveListener<TMessage>(OnMessageReceived<TMessage> listener) where TMessage : IMessage
{
#if UNITY_EDITOR
if (listener == null)
{
Logging.LogError($"Listener of type {typeof(TMessage)} cannot be null", GetType());
return;
}
#endif

var listenerType = typeof(TMessage);
if (TryGetListener<TMessage>(out var messageListener) == false)
{
#if UNITY_EDITOR
Logging.LogWarning($"Could not find a listener for {typeof(TMessage)}", GetType());
#endif
return;
}

messageListener.RemoveListener(listener);

if (messageListener.ListenerCount == 0)
{
interfacesByListenerTypeCache.Remove(listenerType);
listenersByType.Remove(listenerType);
}
}

private bool TryGetListener<TMessage>(out MessageListener listener) where TMessage : IMessage
{
var listenerType = typeof(TMessage);

// Simple base type listener, the most common case
{
if (listenersByType.TryGetValue(listenerType, out listener))
{
return true;
}
}

// Look up base types (abstract classes and such)
{
var listenerBaseType = listenerType.BaseType;
while (listenerBaseType != null && typeof(IMessage).IsAssignableFrom(listenerBaseType))
{
if (listenersByType.TryGetValue(listenerBaseType, out listener))
{
return true;
}

listenerBaseType = listenerBaseType.BaseType;
}
}

// Look up interfaces
{
// ReSharper disable once InlineOutVariableDeclaration
Type[] interfaceTypes;

if (interfacesByListenerTypeCache.TryGetValue(listenerType, out interfaceTypes) == false)
{
interfaceTypes = listenerType.GetInterfaces();
interfacesByListenerTypeCache[listenerType] = interfaceTypes;
}

// ReSharper disable once ForCanBeConvertedToForeach
for (var index = 0; index < interfaceTypes.Length; index++)
{
var interfaceType = interfaceTypes[index];
if (typeof(IMessage).IsAssignableFrom(interfaceType) == false)
{
continue;
}

if (listenersByType.TryGetValue(interfaceType, out listener))
{
return true;
}
}
}

return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace CHARK.GameManagement.Messaging
{
public delegate void OnMessageReceived<in TMessage>(TMessage message) where TMessage : IMessage;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;

namespace CHARK.GameManagement.Messaging
namespace CHARK.GameManagement.Messaging
{
/// <summary>
/// Core application event messaging system.
Expand All @@ -17,11 +15,11 @@ public interface IMessageBus
/// Add a new <paramref name="listener"/> which listens for incoming messages of type
/// <see cref="TMessage"/>.
/// </summary>
public void AddListener<TMessage>(Action<TMessage> listener) where TMessage : IMessage;
public void AddListener<TMessage>(OnMessageReceived<TMessage> listener) where TMessage : IMessage;

/// <summary>
/// Remove existing <paramref name="listener"/> of type <see cref="TMessage"/>.
/// </summary>
public void RemoveListener<TMessage>(Action<TMessage> listener) where TMessage : IMessage;
public void RemoveListener<TMessage>(OnMessageReceived<TMessage> listener) where TMessage : IMessage;
}
}
82 changes: 0 additions & 82 deletions Packages/com.chark.game-management/Runtime/Messaging/MessageBus.cs

This file was deleted.

Loading

0 comments on commit 693f323

Please sign in to comment.