--- /dev/null
+BenchmarkDotNet.Artifacts
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net5.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="BenchmarkDotNet" Version="0.12.1"/>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Impostor.Server\Impostor.Server.csproj"/>
+ </ItemGroup>
+
+</Project>
--- /dev/null
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Running;
+using Impostor.Benchmarks.Tests;
+
+namespace Impostor.Benchmarks
+{
+ internal static class Program
+ {
+ private static void Main(string[] args)
+ {
+ BenchmarkRunner.Run<EventManagerBenchmark>(
+ DefaultConfig.Instance
+ .AddDiagnoser(MemoryDiagnoser.Default)
+ );
+ }
+ }
+}
--- /dev/null
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Impostor.Api.Events;
+using Impostor.Api.Events.Managers;
+using Impostor.Server.Events;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Impostor.Benchmarks.Tests
+{
+ public class EventManagerBenchmark
+ {
+ private IEventManager _eventManager;
+ private IGameEvent _event;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ var services = new ServiceCollection();
+
+ services.AddLogging();
+ services.AddSingleton<IEventManager, EventManager>();
+
+ _event = new GameStartedEvent(null);
+ _eventManager = services.BuildServiceProvider().GetRequiredService<IEventManager>();
+ _eventManager.RegisterListener(new EventListener());
+ _eventManager.RegisterListener(new EventListener());
+ _eventManager.RegisterListener(new EventListener());
+ _eventManager.RegisterListener(new EventListener());
+ _eventManager.RegisterListener(new EventListener());
+ }
+
+ [Benchmark]
+ public async Task Run_1()
+ {
+ for (var i = 0; i < 1; i++)
+ {
+ await _eventManager.CallAsync(_event);
+ }
+ }
+
+ [Benchmark]
+ public async Task Run_1000()
+ {
+ for (var i = 0; i < 1000; i++)
+ {
+ await _eventManager.CallAsync(_event);
+ }
+ }
+
+ [Benchmark]
+ public async Task Run_10000()
+ {
+ for (var i = 0; i < 10000; i++)
+ {
+ await _eventManager.CallAsync(_event);
+ }
+ }
+
+ [Benchmark]
+ public async Task Run_100000()
+ {
+ for (var i = 0; i < 100000; i++)
+ {
+ await _eventManager.CallAsync(_event);
+ }
+ }
+
+ private class EventListener : IEventListener
+ {
+ [EventListener]
+ public void OnGameStarted(IGameStartedEvent e)
+ {
+
+ }
+ }
+ }
+}
-using System;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Events;
internal class EventManager : IEventManager
{
private readonly ConcurrentDictionary<Type, TemporaryEventRegister> _temporaryEventListeners;
+ private readonly ConcurrentDictionary<Type, List<EventHandler>> _cachedEventHandlers;
private readonly ILogger<EventManager> _logger;
private readonly IServiceProvider _serviceProvider;
_logger = logger;
_serviceProvider = serviceProvider;
_temporaryEventListeners = new ConcurrentDictionary<Type, TemporaryEventRegister>();
+ _cachedEventHandlers = new ConcurrentDictionary<Type, List<EventHandler>>();
}
/// <inheritdoc />
register.Add(wrappedEventListener);
}
+ if (eventListeners.Count > 0)
+ {
+ _cachedEventHandlers.TryRemove(typeof(TListener), out _);
+ }
+
return new MultiDisposable(disposes);
}
public bool IsRegistered<TEvent>()
where TEvent : IEvent
{
- return GetHandlers<TEvent>(_serviceProvider).Any();
+ if (_cachedEventHandlers.TryGetValue(typeof(TEvent), out var handlers))
+ {
+ return handlers.Count > 0;
+ }
+
+ return GetHandlers<TEvent>().Any();
}
/// <inheritdoc />
{
try
{
- foreach (var (handler, eventListener) in GetHandlers<T>(_serviceProvider)
- .OrderByDescending(e => e.Listener.Priority))
+ if (!_cachedEventHandlers.TryGetValue(typeof(T), out var handlers))
+ {
+ handlers = CacheEventHandlers<T>();
+ }
+
+ foreach (var (handler, eventListener) in handlers)
{
await eventListener.InvokeAsync(handler, @event, _serviceProvider);
}
}
}
+ private List<EventHandler> CacheEventHandlers<TEvent>()
+ where TEvent : IEvent
+ {
+ var handlers = GetHandlers<TEvent>()
+ .OrderByDescending(e => e.Listener.Priority)
+ .ToList();
+
+ _cachedEventHandlers[typeof(TEvent)] = handlers;
+
+ return handlers;
+ }
+
/// <summary>
/// Get all the event listeners for the given event type.
/// </summary>
- /// <param name="services">Current service provider.</param>
/// <returns>The event listeners.</returns>
- private IEnumerable<EventHandler> GetHandlers<TEvent>(IServiceProvider services)
+ private IEnumerable<EventHandler> GetHandlers<TEvent>()
where TEvent : IEvent
{
var eventType = typeof(TEvent);
}
}
- foreach (var handler in services.GetServices<IEventListener>())
+ foreach (var handler in _serviceProvider.GetServices<IEventListener>())
{
if (handler is IManualEventListener manualEventListener && manualEventListener.CanExecute<TEvent>())
{
using System.Runtime.CompilerServices;
+[assembly:InternalsVisibleTo("Impostor.Benchmarks")]
[assembly:InternalsVisibleTo("Impostor.Tests")]
-[assembly:InternalsVisibleTo("Impostor.Tools.ServerReplay")]
\ No newline at end of file
+[assembly:InternalsVisibleTo("Impostor.Tools.ServerReplay")]
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Plugins.Example", "Impostor.Plugins.Example\Impostor.Plugins.Example.csproj", "{70F97BB7-12A1-4C7A-B2A5-B962D14B813E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Benchmarks", "Impostor.Benchmarks\Impostor.Benchmarks.csproj", "{EA04E386-6CCB-4C52-8C82-64F32C7EB377}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
{70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|Any CPU.Build.0 = Release|Any CPU
{70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.ActiveCfg = Release|Any CPU
{70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.Build.0 = Release|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Debug|x86.Build.0 = Debug|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|x86.ActiveCfg = Release|Any CPU
+ {EA04E386-6CCB-4C52-8C82-64F32C7EB377}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE