Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
TBarendt committed Feb 8, 2024
1 parent 3d8e7f0 commit bd6c597
Show file tree
Hide file tree
Showing 10 changed files with 494 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/[Bb]in/
/[Oo]bj/
/[Bb]uild/
19 changes: 19 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/bin/Debug/net6.0/MessageBus.exe",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
},
],
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "MessageBus.sln"
}
14 changes: 14 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/MessageBus.sln"
],
"problemMatcher": "$msCompile"
}
]
}
108 changes: 108 additions & 0 deletions Example/Example.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
// MIT License
//
// Copyright (c) 2024 Tobias Barendt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//------------------------------------------------------------------------------

public class Example
{
//--------------------------------------------------------------------------
// Messages
public delegate void ExampleMessage();
public delegate void ExampleArguments(int x, int y);

//--------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------
public Example()
{
// Example of how to use it unscoped
UnscopedExample();

// Example on how to use it with a scope
ScopedExample();


}

//--------------------------------------------------------------------------
// UnscopedExample
//--------------------------------------------------------------------------
private void UnscopedExample()
{
//----------------------------------------------------------------------
// Subscribe to a few messages
MessageBus.Subscribe<ExampleMessage>(OnExampleMessage);
MessageBus.Subscribe<ExampleArguments>(OnExampleArguments);


//----------------------------------------------------------------------
// Dispatch messages
MessageBus.Dispatch<ExampleMessage>();
MessageBus.Dispatch<ExampleArguments>(100, 200);

//----------------------------------------------------------------------
// Remove subscriptions
MessageBus.Unsubscribe<ExampleMessage>(OnExampleMessage);
MessageBus.Unsubscribe<ExampleArguments>(OnExampleArguments);
}


//--------------------------------------------------------------------------
// ScopedExample
//--------------------------------------------------------------------------
private void ScopedExample()
{
//----------------------------------------------------------------------
// Subscribe to a few messages
MessageBus.Subscribe<ExampleMessage>("ExampleScope", OnExampleMessage);
MessageBus.Subscribe<ExampleArguments>("ExampleScope", OnExampleArguments);


//----------------------------------------------------------------------
// Dispatch messages
var scopedDispatcher = MessageBus.GetDispatcher("ExampleScope");

scopedDispatcher.Dispatch<ExampleMessage>();
scopedDispatcher.Dispatch<ExampleArguments>(64, 128);

//----------------------------------------------------------------------
// Remove subscriptions
MessageBus.Unsubscribe<ExampleMessage>("ExampleScope", OnExampleMessage);
MessageBus.Unsubscribe<ExampleArguments>("ExampleScope", OnExampleArguments);
}

//--------------------------------------------------------------------------
// OnExampleMessage
//--------------------------------------------------------------------------
private void OnExampleMessage()
{
Console.WriteLine("OnExampleMessage");
}

//--------------------------------------------------------------------------
// OnExampleArguments
//--------------------------------------------------------------------------
private void OnExampleArguments(int x, int y)
{
Console.WriteLine("ExampleArguments with " + x + " and " + y);
}
}
10 changes: 10 additions & 0 deletions MessageBus.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
22 changes: 22 additions & 0 deletions MessageBus.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageBus", "MessageBus.csproj", "{AC802790-5432-4DFD-B9E0-FB66DA790C92}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AC802790-5432-4DFD-B9E0-FB66DA790C92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC802790-5432-4DFD-B9E0-FB66DA790C92}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC802790-5432-4DFD-B9E0-FB66DA790C92}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC802790-5432-4DFD-B9E0-FB66DA790C92}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
204 changes: 204 additions & 0 deletions MessageBus/MessageBus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//------------------------------------------------------------------------------
// MIT License
//
// Copyright (c) 2024 Tobias Barendt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Diagnostics;

//------------------------------------------------------------------------------
// MessageBus
//
// The message bus is a central point for communication between different parts
// of the application. It is used to decouple different parts of the application
// and to allow for a more modular design.
//
public class MessageBus
{
//--------------------------------------------------------------------------
// Singleton instance
private static MessageBus instance = new MessageBus();

//--------------------------------------------------------------------------
// Message Scopes
private Dictionary<string, MessageDispatcher> m_scopes = new();

//--------------------------------------------------------------------------
// GetDispatcher
//
// Returns the message dispatcher for the given scope. IF scoped does not
// already exist it will be created.
//
//--------------------------------------------------------------------------
public static MessageDispatcher GetDispatcher() => instance.Internal_GetDispatcher("");
public static MessageDispatcher GetDispatcher(string scope) => instance.Internal_GetDispatcher(scope);
private MessageDispatcher Internal_GetDispatcher(string scope)
{
if(m_scopes.TryGetValue(scope, out var dispatcher))
return dispatcher;

m_scopes[scope] = new MessageDispatcher(scope);
return m_scopes[scope];
}
//--------------------------------------------------------------------------
// Dispatch
//
// Unscoped dispatch, will dispatch to all unscoped subscribers
// Use GetDispatcher for scoped dispatching
//
//--------------------------------------------------------------------------
public static void Dispatch<MessageType>(params object[] args) where MessageType : System.Delegate => instance.Internal_DispatchEvent(typeof(MessageType).GetHashCode(), args);
public static void Dispatch(System.Type messageType, params object[] args) => instance.Internal_DispatchEvent(messageType.GetHashCode(), args);
private void Internal_DispatchEvent(int type, params object[] args)
{
if(!m_scopes.ContainsKey(""))Internal_GetDispatcher("");
m_scopes[""].Dispatch(type, args);
}

//--------------------------------------------------------------------------
// Subscribe
//--------------------------------------------------------------------------
public static void Subscribe<MessageType>(MessageType subscriber) where MessageType : System.Delegate => instance.Internal_Subscribe("", subscriber);
public static void Subscribe<MessageType>(string scope, MessageType subscriber) where MessageType : System.Delegate => instance.Internal_Subscribe(scope, subscriber);
private void Internal_Subscribe<MessageType>(string scope, MessageType subscriber) where MessageType : System.Delegate
{
if(!m_scopes.ContainsKey(scope))Internal_GetDispatcher(scope);
m_scopes[scope].Subscribe(subscriber);
}

//--------------------------------------------------------------------------
// Unsubscribe
//--------------------------------------------------------------------------
public static void Unsubscribe<MessageType>(MessageType subscriber) where MessageType : System.Delegate => instance.Internal_Unsubscribe("", subscriber);
public static void Unsubscribe<MessageType>(string scope, MessageType subscriber) where MessageType : System.Delegate => instance.Internal_Unsubscribe(scope, subscriber);
private void Internal_Unsubscribe<MessageType>(string scope, MessageType subscriber) where MessageType : System.Delegate
{
if(!m_scopes.ContainsKey(scope))return ;
m_scopes[scope].Unsubscribe(subscriber);
}
}

//------------------------------------------------------------------------------
// IMessageDispatcher
//
// Interface for the message dispatcher, handy for making proxy dispatchers.
//
//------------------------------------------------------------------------------
public interface IMessageDispatcher
{
public void Dispatch<MessageType>(params object[] args) where MessageType : System.Delegate;
public void Dispatch(System.Type messageType, params object[] args);
public void Subscribe<MessageType>(MessageType subscriber) where MessageType : System.Delegate;
public void Unsubscribe<MessageType>(MessageType subscriber) where MessageType : System.Delegate;
}

//------------------------------------------------------------------------------
// MessageDispatcher
//
// The message dispatcher is used to dispatch messages to the correct handlers.
//
//------------------------------------------------------------------------------
public class MessageDispatcher : IMessageDispatcher
{
//--------------------------------------------------------------------------
// Properties
public string Scope {get; private set;}

//--------------------------------------------------------------------------
// Subscribers
private Dictionary<int, HashSet<System.Delegate>> m_subscribers = new();

//--------------------------------------------------------------------------
// Cache
private List<System.Delegate> m_delegateList = new(8);

//--------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------
public MessageDispatcher(string scope)
{
Scope = scope;
}

//--------------------------------------------------------------------------
// Subscribe
//--------------------------------------------------------------------------
public void Subscribe<MessageType>(MessageType subscriber) where MessageType : System.Delegate
{
// Find message type set
int type = subscriber.GetType().GetHashCode();
if(!m_subscribers.TryGetValue(type, out var messageSet))
{
// First time we see this message type, create a new set
messageSet = new HashSet<System.Delegate>();
m_subscribers[type] = messageSet;
}

// Add listener
Debug.Assert(!messageSet.Contains(subscriber as System.Delegate), "Listener added twice! [" + subscriber.GetType() + "]");
messageSet.Add(subscriber as System.Delegate);
}

//--------------------------------------------------------------------------
// Unsubscribe
//--------------------------------------------------------------------------
public void Unsubscribe<MessageType>(MessageType subscriber) where MessageType : System.Delegate
{
// Find message type set
int type = subscriber.GetType().GetHashCode();
if(m_subscribers.TryGetValue(type, out var messageSet))
{
messageSet.Remove(subscriber as System.Delegate);
if(messageSet.Count == 0)
m_subscribers.Remove(type);
}
}

//--------------------------------------------------------------------------
// DispatchEvent
//--------------------------------------------------------------------------
public void Dispatch<MessageType>(params object[] args) where MessageType : System.Delegate => Internal_DispatchEvent(typeof(MessageType).GetHashCode(), args);
public void Dispatch(System.Type messageType, params object[] args) => Internal_DispatchEvent(messageType.GetHashCode(), args);
public void Dispatch(int type, params object[] args) => Internal_DispatchEvent(type, args);
private void Internal_DispatchEvent(int type, params object[] args)
{
if(m_subscribers.TryGetValue(type, out var messageSet))
{
if(messageSet.Count > 0)
{
m_delegateList.Clear();
foreach(System.Delegate dg in messageSet)m_delegateList.Add(dg);

for(int i = 0; i < m_delegateList.Count; i++)
{
try
{
m_delegateList[i].Method.Invoke(m_delegateList[i].Target, args);
}
catch(System.Exception e)
{
Debug.Assert(false, "Error dispatching message: " + e.Message);
}
}
}
}
}
}
Loading

0 comments on commit bd6c597

Please sign in to comment.