{"id":25873290,"url":"https://github.com/annulusgames/zeromessenger","last_synced_at":"2025-04-06T07:10:20.941Z","repository":{"id":260102233,"uuid":"879549968","full_name":"annulusgames/ZeroMessenger","owner":"annulusgames","description":"Zero-allocation, extremely fast in-memory messaging library for .NET and Unity.","archived":false,"fork":false,"pushed_at":"2025-02-17T14:24:10.000Z","size":713,"stargazers_count":127,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-06T07:10:04.768Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/annulusgames.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-28T05:46:11.000Z","updated_at":"2025-04-03T19:57:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"9c4edcfa-f675-488b-874f-b69a6ac7cbe5","html_url":"https://github.com/annulusgames/ZeroMessenger","commit_stats":null,"previous_names":["annulusgames/zeromessenger","yn01dev/zeromessenger","yn01-dev/zeromessenger","nuskey8/zeromessenger"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/annulusgames%2FZeroMessenger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/annulusgames%2FZeroMessenger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/annulusgames%2FZeroMessenger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/annulusgames%2FZeroMessenger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/annulusgames","download_url":"https://codeload.github.com/annulusgames/ZeroMessenger/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247445669,"owners_count":20939958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-02T08:34:48.095Z","updated_at":"2025-04-06T07:10:20.894Z","avatar_url":"https://github.com/annulusgames.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Zero Messenger\n Zero-allocation, extremely fast in-memory messaging library for .NET and Unity.\n\n[![NuGet](https://img.shields.io/nuget/v/ZeroMessenger.svg)](https://www.nuget.org/packages/ZeroMessenger)\n[![Releases](https://img.shields.io/github/release/AnnulusGames/ZeroMessenger.svg)](https://github.com/AnnulusGames/ZeroMessenger/releases)\n[![license](https://img.shields.io/badge/LICENSE-MIT-green.svg)](LICENSE)\n\nEnglish | [日本語](README_JA.md)\n\n## Overview\n\nZero Messenger is a high-performance messaging library for .NET and Unity. It provides `MessageBroker\u003cT\u003e` as an easy-to-use event system for subscribing and unsubscribing, as well as support for implementing the Pub/Sub pattern with `IMessagePublisher\u003cT\u003e/IMessageSubscriber\u003cT\u003e`.\n\nZero Messenger is designed with performance as a priority, achieving faster `Publish()` operations than libraries such as [MessagePipe](https://github.com/Cysharp/MessagePipe) and [VitalRouter](https://github.com/hadashiA/VitalRouter). Moreover, there are no allocations during publishing.\n\n![img](docs/img_benchmark_publish_graph.png)\n\n![img](docs/img_benchmark_publish.png)\n\nAdditionally, it minimizes allocations when constructing message pipelines compared to other libraries. Below is a benchmark result from executing `Subscribe/Dispose` 10,000 times.\n\n![img](docs/img_benchmark_subscribe.png)\n\n## Installation\n\n### NuGet Packages\n\nZero Messenger requires .NET Standard 2.1 or later. The package is available on NuGet.\n\n### .NET CLI\n\n```ps1\ndotnet add package ZeroMessenger\n```\n\n### Package Manager\n\n```ps1\nInstall-Package ZeroMessenger\n```\n\n### Unity\n\nYou can use Zero Messenger in Unity by utilizing NuGetForUnity. For more details, see the [Unity](#unity-1) section.\n\n## Quick Start\n\nYou can easily implement global Pub/Sub using `MessageBroker\u003cT\u003e.Default`.\n\n```cs\nusing System;\nusing ZeroMessenger;\n\n// Subscribe to messages\nvar subscription = MessageBroker\u003cMessage\u003e.Default.Subscribe(x =\u003e\n{\n    Console.WriteLine(x.Text);\n});\n\n// Publish a message\nMessageBroker\u003cMessage\u003e.Default.Publish(new Message(\"Hello!\"));\n\n// Unsubscribe\nsubscription.Dispose();\n\n// Type used for the message\npublic record struct Message(string Text) { }\n```\n\nAdditionally, an instance of `MessageBroker\u003cT\u003e` can be used similarly to `event` or Rx's `Subject\u003cT\u003e`.\n\n```cs\nvar broker = new MessageBroker\u003cint\u003e();\n\nbroker.Subscribe(x =\u003e\n{\n    Console.WriteLine(x);\n});\n\nbroker.Publish(10);\n\nbroker.Dispose();\n```\n\n## Dependency Injection\n\nBy adding Zero Messenger to a DI container, you can easily implement Pub/Sub between services.\n\nZero Messenger supports Pub/Sub on `Microsoft.Extensions.DependencyInjection`, which requires the [ZeroMessenger.DependencyInjection](https://www.nuget.org/packages/ZeroMessenger.DependencyInjection/) package.\n\n#### .NET CLI\n\n```ps1\ndotnet add package ZeroMessenger.DependencyInjection\n```\n\n#### Package Manager\n\n```ps1\nInstall-Package ZeroMessenger.DependencyInjection\n```\n\n### Generic Host\n\nAdding `services.AddZeroMessenger()` registers Zero Messenger in `IServiceCollection`. The following example demonstrates Pub/Sub implementation on a Generic Host.\n\n```cs\nusing ZeroMessenger;\nusing ZeroMessenger.DependencyInjection;\n\nHost.CreateDefaultBuilder()\n    .ConfigureServices((context, services) =\u003e\n    {\n        // Add Zero Messenger\n        services.AddZeroMessenger();\n\n        services.AddSingleton\u003cServiceA\u003e();\n        services.AddSingleton\u003cServiceB\u003e();\n    })\n    .Build()\n    .Run();\n\npublic record struct Message(string Text) { }\n\npublic class ServiceA\n{\n    IMessagePublisher\u003cMessage\u003e publisher;\n\n    public ServiceA(IMessagePublisher\u003cMessage\u003e publisher)\n    {\n        this.publisher = publisher;\n    }\n\n    public Task SendAsync(CancellationToken cancellationToken = default)\n    {\n        publisher.Publish(new Message(\"Hello!\"));\n    }\n}\n\npublic class ServiceB : IDisposable\n{\n    IDisposable subscription;\n\n    public ServiceB(IMessageSubscriber\u003cMessage\u003e subscriber)\n    {\n        subscription = subscriber.Subscribe(x =\u003e\n        {\n            Console.WriteLine(x);\n        });\n    }\n\n    public void Dispose()\n    {\n        subscription.Dispose();\n    }\n}\n```\n\n## Publisher/Subscriber\n\nThe interfaces used for Pub/Sub are `IMessagePublisher\u003cT\u003e` and `IMessageSubscriber\u003cT\u003e`. The `MessageBroker\u003cT\u003e` implements both of these interfaces.\n\n```cs\npublic interface IMessagePublisher\u003cT\u003e\n{\n    void Publish(T message, CancellationToken cancellationToken = default);\n    ValueTask PublishAsync(T message, AsyncPublishStrategy publishStrategy = AsyncPublishStrategy.Parallel, CancellationToken cancellationToken = default);\n}\n\npublic interface IMessageSubscriber\u003cT\u003e\n{\n    IDisposable Subscribe(MessageHandler\u003cT\u003e handler);\n    IDisposable SubscribeAwait(AsyncMessageHandler\u003cT\u003e handler, AsyncSubscribeStrategy subscribeStrategy = AsyncSubscribeStrategy.Sequential);\n}\n```\n\n### IMessagePublisher\n\n`IMessagePublisher\u003cT\u003e` is an interface for publishing messages. You can publish messages using `Publish()`, and with `PublishAsync()`, you can wait for all processing to complete.\n\n```cs\nIMessagePublisher\u003cMessage\u003e publisher;\n\n// Publish a message (Fire-and-forget)\npublisher.Publish(new Message(\"Foo!\"));\n\n// Publish a message and wait for all subscribers to finish processing\nawait publisher.PublishAsync(new Message(\"Bar!\"), AsyncPublishStrategy.Parallel, cancellationToken);\n```\n\nYou can specify `AsyncPublishStrategy` to change how asynchronous message handlers are handled.\n\n| `AsyncPublishStrategy`            | -                                                                          |\n| --------------------------------- | -------------------------------------------------------------------------- |\n| `AsyncPublishStrategy.Parallel`   | All asynchronous message handlers are executed in parallel.                |\n| `AsyncPublishStrategy.Sequential` | Asynchronous message handlers are queued and executed one by one in order. |\n\n### IMessageSubscriber\n\n`IMessageSubscriber\u003cT\u003e` is an interface for subscribing to messages. It provides an extension method `Subscribe()` that accepts an `Action\u003cT\u003e`, allowing you to easily subscribe using lambda expressions. You can unsubscribe by calling `Dispose()` on the returned `IDisposable`.\n\n```cs\nIMessageSubscriber\u003cMessage\u003e subscriber;\n\n// Subscribe to messages\nvar subscription = subscriber.Subscribe(x =\u003e\n{\n    Console.WriteLine(x.Text);\n});\n\n// Unsubscribe\nsubscription.Dispose();\n```\n\nYou can also perform asynchronous processing within the subscription using `SubscribeAwait()`.\n\n```cs\nvar subscription = subscriber.SubscribeAwait(async (x, ct) =\u003e\n{\n    await FooAsync(x, ct);\n}, AsyncSubscribeStrategy.Sequential);\n```\n\nBy specifying `AsyncSubscribeStrategy`, you can change how messages are handled when received during processing.\n\n| `AsyncSubscribeStrategy`            | -                                                            |\n| ----------------------------------- | ------------------------------------------------------------ |\n| `AsyncSubscribeStrategy.Sequential` | Messages are queued and executed in order.                   |\n| `AsyncSubscribeStrategy.Parallel`   | Messages are executed in parallel.                           |\n| `AsyncSubscribeStrategy.Switch`     | Cancels the ongoing processing and executes the new message. |\n| `AsyncSubscribeStrategy.Drop`       | Ignores new messages during ongoing processing.              |\n\n## Filter\n\nFilters allow you to add processing before and after message handling.\n\n### Creating a Filter\n\nTo create a new filter, define a class that implements `IMessageFilter\u003cT\u003e`.\n\n```cs\npublic class NopFilter\u003cT\u003e : IMessageFilter\u003cT\u003e\n{\n    public async ValueTask InvokeAsync(T message, CancellationToken cancellationToken, Func\u003cT, CancellationToken, ValueTask\u003e next)\n    {\n        try\n        {\n            // Call the next processing step\n            await next(message, cancellationToken);\n        }\n        catch\n        {\n            throw;\n        }\n        finally\n        {\n            \n        }\n    }\n}\n```\n\nThe definition of `IMessageFilter\u003cT\u003e` adopts the async decorator pattern, which is also used in [ASP.NET Core middleware](https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0).\n\nHere’s an example of a filter that adds logging before and after processing.\n\n```cs\npublic class LoggingFilter\u003cT\u003e : IMessageFilter\u003cT\u003e\n{\n    public async ValueTask InvokeAsync(T message, CancellationToken cancellationToken, Func\u003cT, CancellationToken, ValueTask\u003e next)\n    {\n        Console.WriteLine(\"Before\");\n        await next(message, cancellationToken);\n        Console.WriteLine(\"After\");\n    }\n}\n```\n\n### Adding Filters\n\nThere are several ways to add a created filter.\n\nIf adding directly to `MessageBroker\u003cT\u003e`, use `AddFilter\u003cT\u003e()`. The order of filter application will follow the order of addition.\n\n```cs\nvar broker = new MessageBroker\u003cint\u003e();\n\n// Add a filter\nbroker.AddFilter\u003cLoggingFilter\u003cint\u003e\u003e();\n```\n\nTo add a global filter to a publisher in the DI container, configure it within the `AddZeroMessenger()` method.\n\n```cs\nHost.CreateDefaultBuilder()\n    .ConfigureServices((context, services) =\u003e\n    {\n        services.AddZeroMessenger(messenger =\u003e\n        {\n            // Specify the type to add\n            messenger.AddFilter\u003cLoggingFilter\u003cMessage\u003e\u003e();\n\n            // Add with open generics\n            messenger.AddFilter(typeof(LoggingFilter\u003c\u003e));\n        });\n    })\n    .Build()\n    .Run();\n```\n\nTo add individual filters when subscribing, you can use the `WithFilter\u003cT\u003e() / WithFilters()` extension methods.\n\n```cs\nIMessageSubscriber\u003cMessage\u003e subscriber;\n\nsubscriber\n    .WithFilter\u003cLoggingFilter\u003cMessage\u003e\u003e()\n    .Subscribe(x =\u003e\n    {\n        \n    });\n```\n\n### PredicateFilter\n\nZero Messenger provides `PredicateFilter\u003cT\u003e`. When you pass a `Predicate\u003cT\u003e` as an argument to `AddFilter\u003cT\u003e()` or `WithFilter\u003cT\u003e()`, a `PredicateFilter\u003cT\u003e` created based on that predicate is automatically added.\n\n```cs\npublic record struct FooMessage(int Value);\n\nIMessageSubscriber\u003cFooMessage\u003e subscriber;\n\nsubscriber\n    .WithFilter(x =\u003e x.Value \u003e= 0) // Exclude values less than 0\n    .Subscribe(x =\u003e\n    {\n        \n    });\n```\n\n## R3\n\nZero Messenger supports integration with [Cysharp/R3](https://github.com/Cysharp/R3). To enable this feature, add the `ZeroMessenger.R3` package.\n\n### .NET CLI\n\n```ps1\ndotnet add package ZeroMessenger.R3\n```\n\n### Package Manager\n\n```ps1\nInstall-Package ZeroMessenger.R3\n```\n\nBy adding ZeroMessenger.R3, you gain access to operators for converting `IMessageSubscriber\u003cT\u003e` to `Observable\u003cT\u003e` and connecting `Observable\u003cT\u003e` to `IMessagePublisher\u003cT\u003e`.\n\n```cs\n// Convert IMessageSubscriber\u003cT\u003e to Observable\u003cT\u003e\nsubscriber.ToObservable()\n    .Subscribe(x =\u003e { });\n\n// Subscribe to Observable\u003cT\u003e and convert it to IMessagePublisher\u003cT\u003e's Publish()\nobservable.SubscribeToPublish(publisher);\n\n// SubscribeAwait to Observable\u003cT\u003e and convert it to IMessagePublisher\u003cT\u003e's PublishAsync()\nobservable.SubscribeAwaitToPublish(publisher, AwaitOperation.Sequential, AsyncPublishStrategy.Parallel);\n```\n\n## Unity\n\nYou can use Zero Messenger in Unity by installing NuGet packages via NugetForUnity.\n\n### Requirements\n\n* Unity 2021.3 or later\n\n### Installation\n\n1. Install [NugetForUnity](https://github.com/GlitchEnzo/NuGetForUnity).\n\n2. Open the NuGet window by selecting `NuGet \u003e Manage NuGet Packages`, search for the `ZeroMessenger` package, and install it.\n    ![img](docs/img_nugetforunity.png)\n\n### VContainer\n\nThere is also an extension package available for handling Zero Messenger with VContainer's DI container.\n\nTo install ZeroMessenger.VContainer, open the Package Manager window by selecting `Window \u003e Package Manager`, then use `[+] \u003e Add package from git URL` and enter the following URL:\n\n```plaintext\nhttps://github.com/AnnulusGames/ZeroMessenger.git?path=src/ZeroMessenger.Unity/Assets/ZeroMessenger.VContainer\n```\n\nBy introducing ZeroMessenger.VContainer, the `IContainerBuilder` gains the `AddZeroMessenger()` extension method. Calling this method adds Zero Messenger to the DI container, allowing `IMessagePublisher\u003cT\u003e` and `IMessageSubscriber\u003cT\u003e` to be injected.\n\n```cs\nusing VContainer;\nusing VContainer.Unity;\nusing ZeroMessenger.VContainer;\n\npublic class ExampleLifetimeScope : LifetimeScope\n{\n    protected override void Configure(IContainerBuilder builder)\n    {\n        // Add Zero Messenger\n        builder.AddZeroMessenger();\n    }\n}\n```\n\n\u003e [!NOTE]\n\u003e `AddZeroMessenger()` registers using Open Generics, which may not work with IL2CPP versions prior to Unity 2022.1.\n\n## License\n\nThis library is released under the [MIT License](LICENSE).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fannulusgames%2Fzeromessenger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fannulusgames%2Fzeromessenger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fannulusgames%2Fzeromessenger/lists"}