mirror of
https://github.com/NexusForever/NexusForever
synced 2026-08-14 00:26:05 -04:00
Servers now support graceful shutdown. Reworked how console commands are parsed to allow for graceful world server shutdown. Moved WebHost into generic host in world server. Added some additional logging and comments to various managers.
159 lines
6.7 KiB
C#
159 lines
6.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using NLog;
|
|
|
|
namespace NexusForever.Shared.Network.Message
|
|
{
|
|
public delegate void MessageHandlerDelegate(NetworkSession session, IReadable message);
|
|
|
|
public sealed class MessageManager : Singleton<MessageManager>
|
|
{
|
|
private static readonly ILogger log = LogManager.GetCurrentClassLogger();
|
|
|
|
private delegate IReadable MessageFactoryDelegate();
|
|
|
|
private ImmutableDictionary<GameMessageOpcode, MessageFactoryDelegate> clientMessageFactories;
|
|
private ImmutableDictionary<Type, GameMessageOpcode> serverMessageOpcodes;
|
|
|
|
private ImmutableDictionary<GameMessageOpcode, MessageHandlerDelegate> clientMessageHandlers;
|
|
|
|
private MessageManager()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialise <see cref="MessageManager"/> and any related resources.
|
|
/// </summary>
|
|
public void Initialise()
|
|
{
|
|
if (clientMessageFactories != null)
|
|
throw new InvalidOperationException();
|
|
|
|
log.Info("Initialisng message manager...");
|
|
|
|
InitialiseMessages();
|
|
InitialiseMessageHandlers();
|
|
}
|
|
|
|
private void InitialiseMessages()
|
|
{
|
|
var messageFactories = new Dictionary<GameMessageOpcode, MessageFactoryDelegate>();
|
|
var messageOpcodes = new Dictionary<Type, GameMessageOpcode>();
|
|
|
|
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()
|
|
.Concat(Assembly.GetEntryAssembly().GetTypes()))
|
|
{
|
|
MessageAttribute attribute = type.GetCustomAttribute<MessageAttribute>();
|
|
if (attribute == null)
|
|
continue;
|
|
|
|
if (typeof(IReadable).IsAssignableFrom(type))
|
|
{
|
|
NewExpression @new = Expression.New(type.GetConstructor(Type.EmptyTypes));
|
|
messageFactories.Add(attribute.Opcode, Expression.Lambda<MessageFactoryDelegate>(@new).Compile());
|
|
}
|
|
if (typeof(IWritable).IsAssignableFrom(type))
|
|
messageOpcodes.Add(type, attribute.Opcode);
|
|
}
|
|
|
|
clientMessageFactories = messageFactories.ToImmutableDictionary();
|
|
serverMessageOpcodes = messageOpcodes.ToImmutableDictionary();
|
|
log.Info($"Initialised {clientMessageFactories.Count} message {(clientMessageFactories.Count == 1 ? "factory" : "factories")}.");
|
|
log.Info($"Initialised {serverMessageOpcodes.Count} message(s).");
|
|
}
|
|
|
|
private void InitialiseMessageHandlers()
|
|
{
|
|
var messageHandlers = new Dictionary<GameMessageOpcode, MessageHandlerDelegate>();
|
|
|
|
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()
|
|
.Concat(Assembly.GetEntryAssembly().GetTypes()))
|
|
{
|
|
foreach (MethodInfo method in type.GetMethods())
|
|
{
|
|
if (method.DeclaringType != type)
|
|
continue;
|
|
|
|
MessageHandlerAttribute attribute = method.GetCustomAttribute<MessageHandlerAttribute>();
|
|
if (attribute == null)
|
|
continue;
|
|
|
|
ParameterExpression sessionParameter = Expression.Parameter(typeof(NetworkSession));
|
|
ParameterExpression messageParameter = Expression.Parameter(typeof(IReadable));
|
|
|
|
ParameterInfo[] parameterInfo = method.GetParameters();
|
|
|
|
if (method.IsStatic)
|
|
{
|
|
#region Debug
|
|
Debug.Assert(parameterInfo.Length == 2);
|
|
Debug.Assert(typeof(NetworkSession).IsAssignableFrom(parameterInfo[0].ParameterType));
|
|
Debug.Assert(typeof(IReadable).IsAssignableFrom(parameterInfo[1].ParameterType));
|
|
#endregion
|
|
|
|
MethodCallExpression call = Expression.Call(method,
|
|
Expression.Convert(sessionParameter, parameterInfo[0].ParameterType),
|
|
Expression.Convert(messageParameter, parameterInfo[1].ParameterType));
|
|
|
|
Expression<MessageHandlerDelegate> lambda =
|
|
Expression.Lambda<MessageHandlerDelegate>(call, sessionParameter, messageParameter);
|
|
|
|
messageHandlers.Add(attribute.Opcode, lambda.Compile());
|
|
}
|
|
else
|
|
{
|
|
#region Debug
|
|
Debug.Assert(parameterInfo.Length == 1);
|
|
Debug.Assert(typeof(NetworkSession).IsAssignableFrom(type));
|
|
Debug.Assert(typeof(IReadable).IsAssignableFrom(parameterInfo[0].ParameterType));
|
|
#endregion
|
|
|
|
MethodCallExpression call = Expression.Call(
|
|
Expression.Convert(sessionParameter, type),
|
|
method,
|
|
Expression.Convert(messageParameter, parameterInfo[0].ParameterType));
|
|
|
|
Expression<MessageHandlerDelegate> lambda =
|
|
Expression.Lambda<MessageHandlerDelegate>(call, sessionParameter, messageParameter);
|
|
|
|
messageHandlers.Add(attribute.Opcode, lambda.Compile());
|
|
}
|
|
}
|
|
}
|
|
|
|
clientMessageHandlers = messageHandlers.ToImmutableDictionary();
|
|
log.Info($"Initialised {clientMessageHandlers.Count} message handler(s).");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return <see cref="IReadable"/> model for incoming packet with <see cref="GameMessageOpcode"/>.
|
|
/// </summary>
|
|
public IReadable GetMessage(GameMessageOpcode opcode)
|
|
{
|
|
return clientMessageFactories.TryGetValue(opcode, out MessageFactoryDelegate factory)
|
|
? factory.Invoke() : null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return <see cref="GameMessageOpcode"/> for outgoing <see cref="IWritable"/> model.
|
|
/// </summary>
|
|
public bool GetOpcode(IWritable message, out GameMessageOpcode opcode)
|
|
{
|
|
return serverMessageOpcodes.TryGetValue(message.GetType(), out opcode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return <see cref="MessageHandlerDelegate"/> delegate for incoming packet with <see cref="GameMessageOpcode"/>.
|
|
/// </summary>
|
|
public MessageHandlerDelegate GetMessageHandler(GameMessageOpcode opcode)
|
|
{
|
|
return clientMessageHandlers.TryGetValue(opcode, out MessageHandlerDelegate handler)
|
|
? handler : null;
|
|
}
|
|
}
|
|
}
|