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

Привет!

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

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

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


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

Посоветуйте, пожалуйста, чем сейчас пользуются для таких целей в мире .NET?
  • Вопрос задан
  • 173 просмотра
Решения вопроса 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, как альтернативный способ избавиться от жуткой лапши
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

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

Войти через центр авторизации
Похожие вопросы
16 авг. 2022, в 23:20
500 руб./за проект
16 авг. 2022, в 22:28
60000 руб./за проект