Какую актуальную библиотеку можно использовать в роли брокера сообщений внутри .NET приложения?

Привет!

Решил немного окунуться в разработку игр на Unity. Обратил внимание, что код во всех туториалах и обучалках - отвратительный. Методы каждого игрового объекта делаются публичными, состояние меняется из 100500 разных мест, везде жуткая лапша и сложности рефакторинга. Видимо, в инди так принято, но я хочу попробовать делать по-уму. А именно - иметь внутри игрового приложения шину/брокера сообщений. Пусть туда кидают эвенты все желающие, а те, кому необходимо что-то знать - подписываются и получают все необходимое.

В принципе, штатные средства C# позволяют написать свою систему мессаджей. Но я трезво оцениваю свою джуниорские способности: работать будет, но медленно. Плюс потрачу на написание хз сколько времени, а я в Unity вкатываюсь совсем за другим :)

Поэтому хотелось бы подключить в проект готовую библиотеку для обмена сообщениями, которая будет:
  • очень быстрой
  • очень эффективной по памяти и процессору (чтобы не вешало мобильные устройства)
  • бесплатной (желательно опенсорс)
  • поддерживаемой


Повторюсь, работать все это будет внутри приложения, без каких-либо внешних брокеров.

Посоветуйте, пожалуйста, чем сейчас пользуются для таких целей в мире .NET?
  • Вопрос задан
  • 183 просмотра
Решения вопроса 2
AlexanderYudakov
@AlexanderYudakov
C#, 1С, Android, TypeScript
Решил размяться.

Вот шина:
MessageBus.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace SimpleMessageBus
{
    /// <summary>
    /// Рассылает события заинтересованным подписчикам
    /// в рамках одного приложения.
    /// </summary>
    /// <remarks>Используются WeakReference, чтобы не было утечек памяти
    /// на случай, если кто-то забудет отписаться. Поэтому не рекомендуется
    /// использовать лямбда-обработчики событий.</remarks>
    public static class MessageBus
    {
        private static readonly Dictionary<string, List<WeakReference<Action<string, object>>>> Subscribers =
            new Dictionary<string, List<WeakReference<Action<string, object>>>>();

        /// <summary>
        /// Оформляет подписку на событие
        /// </summary>
        /// <param name="topic">Тип события</param>
        /// <param name="handler">Подписчик</param>
        public static void Subscribe(string topic,
            Action<string, object> handler)
        {
            if (String.IsNullOrEmpty(topic))
                throw new ArgumentNullException(nameof(topic));

            if (handler == null)
                throw new ArgumentNullException(nameof(handler));

            List<WeakReference<Action<string, object>>> handlers;
            lock (Subscribers)
                if (!Subscribers.TryGetValue(topic, out handlers))
                    handlers = Subscribers[topic] = new List<WeakReference<Action<string, object>>>();

            lock (handlers)
                handlers.Add(new WeakReference<Action<string, object>>(handler));
        }

        /// <summary>
        /// Отменяет подписку на событие
        /// </summary>
        /// <param name="topic">Тип события</param>
        /// <param name="handler">Подписчик</param>
        public static void Unsubscribe(string topic,
            Action<string, object> handler)
        {
            if (String.IsNullOrEmpty(topic))
                throw new ArgumentNullException(nameof(topic));

            if (handler == null)
                throw new ArgumentNullException(nameof(handler));

            List<WeakReference<Action<string, object>>> list;
            lock (Subscribers)
                if (!Subscribers.TryGetValue(topic, out list))
                    return;

            lock (list)
            {
                var i = 0;
                while (i < list.Count)
                {
                    var reference = list[i];
                    if (!reference.TryGetTarget(out var target))
                        list.RemoveAt(i); // Заодно очищаем список от мертвых подписчиков
                    else if (target == handler)
                    {
                        list.RemoveAt(i);
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// Оповещает подписчиков о наступлении события
        /// </summary>
        /// <param name="topic">Тип события</param>
        /// <param name="data">Данные события</param>
        public static void Publish(string topic, object data = null)
        {
            if (String.IsNullOrEmpty(topic))
                throw new ArgumentNullException(nameof(topic));

            List<WeakReference<Action<string, object>>> list;
            lock (Subscribers)
                if (!Subscribers.TryGetValue(topic, out list))
                    return;

            var handlers = new List<Action<string, object>>();
            lock (list)
            {
                var i = 0;
                while (i < list.Count)
                {
                    var reference = list[i];
                    if (!reference.TryGetTarget(out var target))
                        list.RemoveAt(i);
                    else
                    {
                        handlers.Add(target);
                        ++i;
                    }
                }
            }

            // В Unity вызывать обработчики событий, наверное,
            // нужно как-то так:
            UnityEngine.WSA.Application.InvokeOnAppThread(() =>
            // А в обычном консольном приложении так:
            // Task.Run(() =>
            {
                foreach (var handler in handlers)
                {
                    try
                    {
                        handler.Invoke(topic, data);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex);
                    }
                }
            }, false);
        }
    }
}


Вот пример использования:
using System.Diagnostics;
using System.Threading.Tasks;

namespace SimpleMessageBus
{
    public static class Program
    {
        public static async Task Main(string[] args)
        {
            Debug.WriteLine("Started.");

            MessageBus.Subscribe("Rain", RainHandler);
            MessageBus.Publish("Rain");

            await Task.Delay(100); // Ждем, пока событие поступит
            MessageBus.Unsubscribe("Rain", RainHandler);
            
            Debug.WriteLine("Finished.");
        }

        private static void RainHandler(string topic, object data)
        {
            Debug.WriteLine("Event: " + topic);
        }
    }
}


P.S. Бесплатно. Опенсорс.

Upd. Добавил вызов UnityEngine.WSA.Application.InvokeOnAppThread() — все-таки у нас целевая платформа Unity.
Ответ написан
vabka
@vabka Куратор тега C#
Токсичный шарпист
Посмотри на System.Threading.Channels и rxNET.
И ещё посмотри на ECS, как альтернативный способ избавиться от жуткой лапши
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы