{"id":23380495,"url":"https://github.com/sov3rain/messager","last_synced_at":"2025-04-10T22:42:39.686Z","repository":{"id":57203784,"uuid":"146302889","full_name":"Sov3rain/Messager","owner":"Sov3rain","description":"Simple messaging system for Unity, based on the event aggregator design pattern.","archived":false,"fork":false,"pushed_at":"2023-02-18T12:25:17.000Z","size":99,"stargazers_count":18,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-10T03:01:13.676Z","etag":null,"topics":["aggregator","architecture","csharp","event-driven","events","game","game-engine","gamedev","messaging","observer","pattern","publisher","script","subscriber","unity","unity3d"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/com.sov3rain.messager","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/Sov3rain.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}},"created_at":"2018-08-27T13:40:50.000Z","updated_at":"2025-01-15T12:51:30.000Z","dependencies_parsed_at":"2022-09-17T14:52:45.919Z","dependency_job_id":null,"html_url":"https://github.com/Sov3rain/Messager","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sov3rain%2FMessager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sov3rain%2FMessager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sov3rain%2FMessager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sov3rain%2FMessager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sov3rain","download_url":"https://codeload.github.com/Sov3rain/Messager/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248312208,"owners_count":21082638,"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":["aggregator","architecture","csharp","event-driven","events","game","game-engine","gamedev","messaging","observer","pattern","publisher","script","subscriber","unity","unity3d"],"created_at":"2024-12-21T20:16:28.148Z","updated_at":"2025-04-10T22:42:39.655Z","avatar_url":"https://github.com/Sov3rain.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Messager\n\n\u003ca href=\"https://www.npmjs.com/package/com.sov3rain.messager\" target=\"_blank\"\u003e\u003cimg alt=\"npmjs\" src=\"https://img.shields.io/npm/v/com.sov3rain.messager\"\u003e\u003c/a\u003e ![unity-version](https://img.shields.io/badge/unity-2019.4%2B-lightgrey)\n\nThis implementation of the event aggregator pattern tries to overcome the limitations of traditional event handling by providing a central place to publish and subscribe for events. It takes care of registering, unregistering and invoking events and thus decoupling publishers and subscribers.\n\n## Installation\n\nThis package uses Unity's [scoped registry](https://docs.unity3d.com/Manual/upm-scoped.html) feature to import packages and is hosted [here](https://www.npmjs.com/package/com.sov3rain.messager). Add the following sections to the package manifest file located in `Packages/manifest.json`.\n\nAdd this object to the `scopedRegistries` section:\n\n```json\n{\n  \"name\": \"sov3rain\",\n  \"url\": \"https://registry.npmjs.com\",\n  \"scopes\": [ \"com.sov3rain\" ]\n}\n```\n\nAdd  this line to the `dependencies` section:\n\n```json\n\"com.sov3rain.messager\": \"1.1.0\"\n```\n\nYour manifest file should look like this now:\n\n```json\n{\n  \"scopedRegistries\": [{\n      \"name\": \"sov3rain\",\n      \"url\": \"https://registry.npmjs.com\",\n      \"scopes\": [ \"com.sov3rain\" ]\n\t}],\n  \"dependencies\": {\n    \"com.sov3rain.messager\": \"1.1.0\",\n    ...\n  }\n}\n```\n\n## Usage\nYour main access point to the *Messager* is through its abstraction layer, which uses extension methods. This methods are available from any `object` by using the `this` keyword. From here you can access the three primary methods: `Listen()`, `Dispatch()` and `Cut()`.\n\n### Create a message\n\nMessages are just plain C# objects. They can be classes or structs. They serve the purpose of creating a unique signature by enforcing Type reference instead of string reference.\n\n```csharp\npublic struct SIMPLE_MESSAGE\n{\n    public int Number;\n}\n```\n\n### Dispatch a message\n\nYou can dispatch a new message by using the `Dispatch()` extension method.\n\n```csharp\nthis.Dispatch(new SIMPLE_MESSAGE { Number: 42 });\n```\n\nThe message type is inferred by the class instance you pass as an argument.\n\n### Start listening for a message\n\nAnywhere in any class, you can listen for a message by using the `Listen\u003cT\u003e()` extension method.  The type you want to listen for is specified through the generic type parameter.\n\n```c#\nthis.Listen\u003cSIMPLE_MESSAGE\u003e(msg =\u003e print($\"Message incoming: {msg.Number}\"));\n```\n\n\u003e Note: You can register anonymous functions safely because they are bonded to the instance registering, so they can be removed later.\n\n### Stop listening for a message\n\nWhen you want to stop being notified when a type of message is dispatched, you can call `Cut()` to unregister the object:\n\n```c#\nthis.Cut\u003cSIMPLE_MESSAGE\u003e();\n```\n\n\u003e Note: when using `Cut`, all listeners registered for that object are removed, as they are referenced by owner.\n\u003e Note 2: remember to always call `Cut()` before destroying an object instance that is still listening to messages to avoid memory leaks.\n\n## Advanced Usage\n\n### Referencing the messager\n\nIn order to reduce boilerplate code and clutter, *Messager* comes with extension methods. You can still get a direct reference in any part of your code by accessing the default static instance:\n\n```csharp\nprivate readonly Messager _messager = Messager.DefaultInstance;\n```\n\n\u003e Note: instantiating your own Messager class could help unit testing or mocking, but for regular use, it's recommended to use the default static instance.\n\u003e\n\u003e ```csharp\n\u003e private readonly Messenger _myMessager = new Messager();\n\u003e ```\n\nThen use that specific reference to add you listener and dispatch messages like with extension methods:\n```csharp\n// Dispatch\n_messager.Dispatch(new SIMPLE_MESSAGE { Number: 42 });\n\n// Listen\n_messager.Listen\u003cSIMPLE_MESSAGE\u003e(\n    owner: this, \n    handler: msg =\u003e print($\"Message incoming: {msg.Number}\");\n);\n\n// Cut\n_messager.Cut\u003cSIMPLE_MESSAGE\u003e(this);\n```\n\n### Changing the Messager instance\n\nYou can change the instance of *Messager* that will be used by the extension methods by accessing the property `Instance` like so:\n\n```c#\nMessagerExtensions.Instance = _myMessager; // Add your custom instance here\n```\n\n## Good Practices\n\n**DO** name your message objects in a `ALL_UPPER` style to differentiate them from your other classes and structs.\n\n`````csharp\n// Bad.\npublic class SimpleMessage { }\n\n// Good.\npublic class SIMPLE_MESSAGE { }\n`````\n\n**DO** use immutable objects as messages to avoid side effects:\n\n```csharp\npublic sealed class IMMUTABLE_MESSAGE\n{\n    public int Number { get; }\n\n    public IMMUTABLE_MESSAGE(int number)\n    {\n        Number = number;\n    }\n}\n\npublic struct IMMUTABLE_STRUCT_MESSAGE\n{\n    public int Number { get; }\n\n    public IMMUTABLE_STRUCT_MESSAGE(int number)\n    {\n        Number = number;\n    }\n}\n```\n\n**DO** cache the message instance if you plan on reusing it multiple times to avoid unnecessary memory allocations:\n\n```csharp\nvar _msg = new SIMPLE_MESSAGE { Number = 42 };\n\n// Example code, don't do that.\nvoid Update()\n{\n    _messager.Dispatch(this, _msg);\n}\n```\n\n## Devtools\n\nMessager comes with a set of devtools to let you better track down message subscriptions and have a timed history of published messages. Devtools are never shipped out in build, you can **only use them in the Editor**.\n\n### Enable devtools\n\nBy default devtools are disabled. You can toggle them with the menu bar under `Messager/Enable Devtools`.\n\n### Devtools Window\n\nYou can open the devtools window with the menu bar under `Messager/Open Devtools Window`. You can dock the window or use it in floating mode. The window itself has two tabs:\n\n#### Message history\n\nWhen you dispatch a message in you application code, a timed record will be created and displayed in the history.\n\n![histo-tab](https://i.ibb.co/rkgv6yT/history-tab.jpg)\n\nFrom here you can see the the type of message, the message timecode (from application start). Will be displayed under the foldout the type of the caller and the calling method, and if the message contains data the json serialiazed payload.\n\nYou can filter the history by message type. Names are case sensitive.\n\n#### Subscribers\n\nAvailable at runtime only, it will show each type of message used and all the subscribers listening to that particular type of message underneath. The number of subscribers will be shown between parentheses. If the subscriber is of type `UnityEngine.Object`, you will be able to click the object field to select the instance in the scene. If the subscriber is not of type `UnityEngine.Object`, you will just see the instance type name.\n\n\u003e Note: if a message has no subscribers at some point, it will not be shown.\n\n![subs-tab](https://i.ibb.co/P5nFPnt/subscribers-tab.jpg)\n\n You can also filter the list by message type via the search field. Names are case sensitive.\n\n## API Reference\n\n### Messager Class\n\n#### Properties\n\n| Name                          | Description                              |\n| ----------------------------- | ---------------------------------------- |\n| `(Messsager) DefaultInstance` | Returns the `Messager` default instance. |\n\n#### Methods\n\n| Name                                                                | Description                                                                                                                                                                                                                                                   |\n| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `Use(Middleware onDispatch, Middleware onLister, Middleware onCut)` | Initialize middlewares. You can register a delegate matching the following signature: `(Type type, object owner, Action next)` where next is the next function in the delegate. Devtools use this middlewares to get notified about dispatch, listen and cut. |\n| `Listen\u003cT\u003e(object owner, Action\u003cT\u003e handler)`                        | Registers a handler. The type you want to listen for must be passed in the generic parameter.                                                                                                                                                                 |\n| `Cut\u003cT\u003e(object owner)`                                              | Remove all handlers owned by the `owner` object for the specified type.                                                                                                                                                                                       |\n| `Dispatch\u003cT\u003e(T payload)`                                            | Dispatch a new message. The type can be inferred from the instance passed as the `payload` parameter.                                                                                                                                                         |\n\n### Messager.Subscription Class\n\n#### Properties\n\n| Name                      | Description                                                                              |\n| ------------------------- | ---------------------------------------------------------------------------------------- |\n| `(object) Owner`          | The reference of the object owning this message.                                         |\n| `(Action\u003cobject\u003e) Action` | The registered callback. Will be invoked when a new message type matching is dispatched. |\n\n#### Constructors\n\n| Name                                                | Description                                                |\n| --------------------------------------------------- | ---------------------------------------------------------- |\n| `Subscription(object owner, Action\u003cobject\u003e action)` | Default constructor. Sets the owner and action properties. |\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsov3rain%2Fmessager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsov3rain%2Fmessager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsov3rain%2Fmessager/lists"}