Test-time reflected event checker

Server UI components work with new system
Events for loading and unloading TorchSessions
Commands report information again.
Catch, log, and rethrow errors that occur in Torch-handled SE events
This commit is contained in:
Westin Miller
2017-08-24 20:24:07 -07:00
parent 4b2fee7614
commit 140000df55
19 changed files with 230 additions and 73 deletions

View File

@@ -17,11 +17,27 @@ namespace Torch.API.Session
/// <returns>The manager that will live in the session, or null if none.</returns>
public delegate IManager SessionManagerFactoryDel(ITorchSession session);
/// <summary>
/// Fired when the given session has been completely loaded or is unloading.
/// </summary>
/// <param name="session">The session</param>
public delegate void TorchSessionLoadDel(ITorchSession session);
/// <summary>
/// Manages the creation and destruction of <see cref="ITorchSession"/> instances for each <see cref="Sandbox.Game.World.MySession"/> created by Space Engineers.
/// </summary>
public interface ITorchSessionManager : IManager
{
/// <summary>
/// Fired when a <see cref="ITorchSession"/> has finished loading.
/// </summary>
event TorchSessionLoadDel SessionLoaded;
/// <summary>
/// Fired when a <see cref="ITorchSession"/> has begun unloading.
/// </summary>
event TorchSessionLoadDel SessionUnloading;
/// <summary>
/// The currently running session
/// </summary>

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Torch.Client;
using Torch.Tests;
using Torch.Utils;
@@ -86,7 +87,7 @@ namespace Torch.Client.Tests
return;
Assert.True(ReflectedManager.Process(field.Field));
if (field.Field.IsStatic)
Assert.NotNull(field.Field.GetValue(null));
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
}
#endregion
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Torch.Tests;
using Torch.Utils;
using Xunit;
@@ -74,7 +75,7 @@ namespace Torch.Server.Tests
return;
Assert.True(ReflectedManager.Process(field.Field));
if (field.Field.IsStatic)
Assert.NotNull(field.Field.GetValue(null));
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
}
#endregion
}

View File

@@ -16,6 +16,7 @@ using Torch.Utils;
using Torch.ViewModels;
using VRage.GameServices;
using VRage.Network;
using VRage.Steam;
namespace Torch.Server.Managers
{
@@ -73,9 +74,9 @@ namespace Torch.Server.Managers
#pragma warning disable 649
[ReflectedEventReplace(typeof(IMyGameServer), nameof(IMyGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
private static Func<ReflectedEventReplacer> _gameServerValidateAuthTicketFactory;
[ReflectedEventReplace(typeof(IMyGameServer), nameof(IMyGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
private static Func<ReflectedEventReplacer> _gameServerUserGroupStatusFactory;
private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer;
private ReflectedEventReplacer _gameServerUserGroupStatusReplacer;

View File

@@ -182,6 +182,9 @@
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Steam">
<HintPath>..\GameBinaries\VRage.Steam.dll</HintPath>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />

View File

@@ -21,6 +21,7 @@ using Sandbox.Game.World;
using SteamSDK;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Torch.Managers;
using Torch.Server.Managers;
@@ -42,14 +43,29 @@ namespace Torch.Server
{
_server = (TorchBase)server;
ChatItems.Items.Clear();
server.SessionLoaded += () =>
var sessionManager = server.Managers.GetManager<ITorchSessionManager>();
sessionManager.SessionLoaded += BindSession;
sessionManager.SessionUnloading += UnbindSession;
}
private void BindSession(ITorchSession session)
{
var multiplayer = server.CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>();
DataContext = multiplayer;
// TODO
// if (multiplayer.ChatHistory is INotifyCollectionChanged ncc)
// ncc.CollectionChanged += ChatHistory_CollectionChanged;
};
Dispatcher.Invoke(() =>
{
var chatMgr = _server?.CurrentSession?.Managers.GetManager<IChatManagerClient>();
if (chatMgr != null)
DataContext = new ChatManagerProxy(chatMgr);
});
}
private void UnbindSession(ITorchSession session)
{
Dispatcher.Invoke(() =>
{
(DataContext as ChatManagerProxy)?.Dispose();
DataContext = null;
});
}
private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
@@ -86,7 +102,7 @@ namespace Torch.Server
var commands = _server.CurrentSession?.Managers.GetManager<Torch.Commands.CommandManager>();
if (commands != null && commands.IsCommand(text))
{
// TODO _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
(DataContext as ChatManagerProxy)?.AddMessage(new TorchChatMessage() { Author = "Server", Message = text });
_server.Invoke(() =>
{
var response = commands.HandleCommandFromServer(text);
@@ -102,8 +118,37 @@ namespace Torch.Server
private void OnMessageEntered_Callback(string response)
{
//if (!string.IsNullOrEmpty(response))
// TODO _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
if (!string.IsNullOrEmpty(response))
(DataContext as ChatManagerProxy)?.AddMessage(new TorchChatMessage() { Author = "Server", Message = response });
}
private class ChatManagerProxy : IDisposable
{
private readonly IChatManagerClient _chatMgr;
public ChatManagerProxy(IChatManagerClient chatMgr)
{
this._chatMgr = chatMgr;
this._chatMgr.MessageRecieved += ChatMgr_MessageRecieved; ;
}
public IList<IChatMessage> ChatHistory { get; } = new ObservableList<IChatMessage>();
/// <inheritdoc />
public void Dispose()
{
_chatMgr.MessageRecieved -= ChatMgr_MessageRecieved;
}
private void ChatMgr_MessageRecieved(TorchChatMessage msg, ref bool consumed)
{
AddMessage(msg);
}
internal void AddMessage(TorchChatMessage msg)
{
ChatHistory.Add(new ChatMessage(DateTime.Now, msg.AuthorSteamId ?? 0, msg.Author, msg.Message));
}
}
}
}

View File

@@ -21,6 +21,7 @@ using Sandbox.ModAPI;
using SteamSDK;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Torch.Managers;
using Torch.Server.Managers;
using Torch.ViewModels;
@@ -43,11 +44,20 @@ namespace Torch.Server
public void BindServer(ITorchServer server)
{
_server = server;
server.SessionLoaded += () =>
var sessionManager = server.Managers.GetManager<ITorchSessionManager>();
sessionManager.SessionLoaded += BindSession;
sessionManager.SessionUnloading += UnbindSession;
}
private void BindSession(ITorchSession session)
{
var multiplayer = server.CurrentSession?.Managers.GetManager<MultiplayerManagerDedicated>();
DataContext = multiplayer;
};
Dispatcher.Invoke(() => DataContext = _server?.CurrentSession?.Managers.GetManager<MultiplayerManagerDedicated>());
}
private void UnbindSession(ITorchSession session)
{
Dispatcher.Invoke(() => DataContext = null);
}
private void KickButton_Click(object sender, RoutedEventArgs e)

View File

@@ -77,7 +77,7 @@ namespace Torch.Tests
return;
Assert.True(ReflectedManager.Process(field.Field));
if (field.Field.IsStatic)
Assert.NotNull(field.Field.GetValue(null));
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
}
#endregion

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Torch.Utils;
using Xunit;
@@ -84,7 +85,7 @@ namespace Torch.Tests
return;
Assert.True(ReflectedManager.Process(field.Field));
if (field.Field.IsStatic)
Assert.NotNull(field.Field.GetValue(null));
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
}
#endregion
}

View File

@@ -117,7 +117,7 @@ namespace Torch.Commands
catch (Exception e)
{
context.Respond(e.Message, "Error", MyFontEnum.Red);
Log.Error($"Command '{SyntaxHelp}' from '{Plugin.Name ?? "Torch"}' threw an exception. Args: {string.Join(", ", context.Args)}");
Log.Error($"Command '{SyntaxHelp}' from '{Plugin?.Name ?? "Torch"}' threw an exception. Args: {string.Join(", ", context.Args)}");
Log.Error(e);
return true;
}

View File

@@ -108,7 +108,7 @@ namespace Torch.Commands
consumed = true;
var player = Torch.GetManager<IMultiplayerManagerBase>().GetPlayerBySteamId(steamId);
var player = Torch.CurrentSession.Managers.GetManager<IMultiplayerManagerBase>().GetPlayerBySteamId(steamId);
if (player == null)
{
_log.Error($"Command {message} invoked by nonexistant player");

View File

@@ -21,7 +21,12 @@ namespace Torch.Commands
[Permission(MyPromoteLevel.None)]
public void Help()
{
var commandManager = ((TorchBase)Context.Torch).CurrentSession.Managers.GetManager<CommandManager>();
var commandManager = Context.Torch.CurrentSession?.Managers.GetManager<CommandManager>();
if (commandManager == null)
{
Context.Respond("Must have an attached session to list commands");
return;
}
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
if (node != null)
@@ -51,7 +56,12 @@ namespace Torch.Commands
[Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")]
public void LongHelp()
{
var commandManager = Context.Torch.Managers.GetManager<CommandManager>();
var commandManager = Context.Torch.CurrentSession?.Managers.GetManager<CommandManager>();
if (commandManager == null)
{
Context.Respond("Must have an attached session to list commands");
return;
}
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
if (node != null)
@@ -96,7 +106,7 @@ namespace Torch.Commands
[Permission(MyPromoteLevel.None)]
public void Plugins()
{
var plugins = Context.Torch.Plugins.Select(p => p.Name);
var plugins = Context.Torch.Managers.GetManager<PluginManager>()?.Plugins.Select(p => p.Name) ?? Enumerable.Empty<string>();
Context.Respond($"Loaded plugins: {string.Join(", ", plugins)}");
}
@@ -128,15 +138,13 @@ namespace Torch.Commands
{
if (i >= 60 && i % 60 == 0)
{
// TODO
// Context.Torch.Multiplayer.SendMessage($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
// yield return null;
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
yield return null;
}
else if (i > 0)
{
// TODO
// if (i < 11)
// Context.Torch.Multiplayer.SendMessage($"Restarting server in {i} second{Pluralize(i)}.");
if (i < 11)
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf($"Restarting server in {i} second{Pluralize(i)}.");
yield return null;
}
else

View File

@@ -15,7 +15,6 @@ using Torch.API;
using Torch.API.Managers;
using Torch.Utils;
using VRage.Game;
using Game = Sandbox.Engine.Platform.Game;
namespace Torch.Managers.ChatManager
{
@@ -37,7 +36,7 @@ namespace Torch.Managers.ChatManager
{
if (MyMultiplayer.Static != null)
{
if (Game.IsDedicated)
if (Sandbox.Engine.Platform.Game.IsDedicated)
{
var scripted = new ScriptedChatMsg()
{
@@ -51,14 +50,15 @@ namespace Torch.Managers.ChatManager
else
MyMultiplayer.Static.SendChatMessage(message);
}
else if (MyHud.Chat != null)
else if (HasHud)
MyHud.Chat.ShowMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", message);
}
/// <inheritdoc />
public void DisplayMessageOnSelf(string author, string message, string font)
{
MyHud.Chat.ShowMessage(author, message, font);
if (HasHud)
MyHud.Chat?.ShowMessage(author, message, font);
MySession.Static.GlobalChatHistory.GlobalChatHistory.Chat.Enqueue(new MyGlobalChatItem()
{
Author = author,
@@ -91,9 +91,9 @@ namespace Torch.Managers.ChatManager
public override void Detach()
{
MyAPIUtilities.Static.MessageEntered -= OnMessageEntered;
if (_chatMessageRecievedReplacer != null && _chatMessageRecievedReplacer.Replaced)
if (_chatMessageRecievedReplacer != null && _chatMessageRecievedReplacer.Replaced && HasHud)
_chatMessageRecievedReplacer.Restore(MyHud.Chat);
if (_scriptedChatMessageRecievedReplacer != null && _scriptedChatMessageRecievedReplacer.Replaced)
if (_scriptedChatMessageRecievedReplacer != null && _scriptedChatMessageRecievedReplacer.Replaced && HasHud)
_scriptedChatMessageRecievedReplacer.Restore(MyHud.Chat);
MyAPIUtilities.Static.MessageEntered -= OfflineMessageReciever;
base.Detach();
@@ -148,11 +148,11 @@ namespace Torch.Managers.ChatManager
};
var consumed = false;
MessageRecieved?.Invoke(torchMsg, ref consumed);
if (!consumed)
if (!consumed && HasHud)
_hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, message);
}
private void Multiplayer_ScriptedChatMessageReceived(string author, string message, string font)
private void Multiplayer_ScriptedChatMessageReceived(string message, string author, string font)
{
var torchMsg = new TorchChatMessage()
{
@@ -163,13 +163,15 @@ namespace Torch.Managers.ChatManager
};
var consumed = false;
MessageRecieved?.Invoke(torchMsg, ref consumed);
if (!consumed)
if (!consumed && HasHud)
_hudChatScriptedMessageReceived.Invoke(MyHud.Chat, author, message, font);
}
private const string _hudChatMessageReceivedName = "Multiplayer_ChatMessageReceived";
private const string _hudChatScriptedMessageReceivedName = "multiplayer_ScriptedChatMessageReceived";
protected static bool HasHud => !Sandbox.Engine.Platform.Game.IsDedicated;
[ReflectedMethod(Name = _hudChatMessageReceivedName)]
private static Action<MyHudChat, ulong, string> _hudChatMessageReceived;
[ReflectedMethod(Name = _hudChatScriptedMessageReceivedName)]

View File

@@ -43,7 +43,7 @@ namespace Torch.Managers.ChatManager
{
if (MyMultiplayer.Static == null)
{
if (targetSteamId == MyGameService.UserId || targetSteamId == 0)
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
MyHud.Chat?.ShowMessage(authorId == MyGameService.UserId ?
(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player") : $"user_{authorId}", message);
return;
@@ -73,7 +73,7 @@ namespace Torch.Managers.ChatManager
{
if (MyMultiplayer.Static == null)
{
if (targetSteamId == MyGameService.UserId || targetSteamId == 0)
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
MyHud.Chat?.ShowMessage(author, message, font);
return;
}
@@ -82,7 +82,7 @@ namespace Torch.Managers.ChatManager
Author = author,
Text = message,
Font = font,
Target = (long)targetSteamId
Target = Sync.Players.TryGetIdentityId(targetSteamId)
};
MyMultiplayerBase.SendScriptedChatMessage(ref scripted);
}
@@ -107,7 +107,12 @@ namespace Torch.Managers.ChatManager
if (MyMultiplayer.Static != null)
{
MyMultiplayer.Static.ChatMessageReceived += MpStaticChatMessageReceived;
_log.Warn("Failed to initialize network intercept, we can't discard chat messages! Falling back to another method.");
_log.Warn(
"Failed to initialize network intercept, we can't discard chat messages! Falling back to another method.");
}
else
{
_log.Debug("Using offline message processor");
}
}

View File

@@ -115,7 +115,7 @@ namespace Torch.Managers
protected void RaiseClientJoined(ulong steamId)
{
var vm = new PlayerViewModel(steamId){State=ConnectionState.Connected};
_log.Info($"Plat {vm.Name} joined ({vm.SteamId}");
_log.Info($"Player {vm.Name} joined ({vm.SteamId}");
Players.Add(steamId, vm);
PlayerJoined?.Invoke(vm);
}

View File

@@ -60,15 +60,9 @@ namespace Torch.Managers
throw;
}
}
/// <summary>
/// Loads the network intercept system
/// </summary>
public override void Attach()
{
Torch.SessionLoaded += OnSessionLoaded;
}
private void OnSessionLoaded()
/// <inheritdoc/>
public override void Attach()
{
if (_init)
return;
@@ -105,6 +99,12 @@ namespace Torch.Managers
_log.Debug("Initialized network intercept");
}
/// <inheritdoc/>
public override void Detach()
{
// TODO reverse what was done in Attach
}
#region Network Intercept
/// <summary>

View File

@@ -10,6 +10,7 @@ using NLog;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Plugins;
using Torch.API.Session;
using Torch.Commands;
using VRage.Collections;
@@ -22,8 +23,8 @@ namespace Torch.Managers
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
[Dependency]
private UpdateManager _updateManager;
[Dependency]
private CommandManager _commandManager;
[Dependency(Optional = true)]
private ITorchSessionManager _sessionManager;
/// <inheritdoc />
public IList<ITorchPlugin> Plugins { get; } = new ObservableList<ITorchPlugin>();
@@ -41,6 +42,11 @@ namespace Torch.Managers
/// </summary>
public void UpdatePlugins()
{
if (_sessionManager != null)
{
_sessionManager.SessionLoaded += AttachCommandsToSession;
_sessionManager.SessionUnloading += DetachCommandsFromSession;
}
foreach (var plugin in Plugins)
plugin.Update();
}
@@ -50,12 +56,32 @@ namespace Torch.Managers
/// </summary>
public override void Detach()
{
if (_sessionManager != null)
{
_sessionManager.SessionLoaded -= AttachCommandsToSession;
_sessionManager.SessionUnloading -= DetachCommandsFromSession;
}
foreach (var plugin in Plugins)
plugin.Dispose();
Plugins.Clear();
}
private void AttachCommandsToSession(ITorchSession session)
{
var cmdMgr = session.Managers.GetManager<CommandManager>();
foreach (ITorchPlugin plugin in Plugins)
cmdMgr?.RegisterPluginCommands(plugin);
}
private void DetachCommandsFromSession(ITorchSession session)
{
var cmdMgr = session.Managers.GetManager<CommandManager>();
foreach (ITorchPlugin plugin in Plugins) {
// cmdMgr?.UnregisterPluginCommands(plugin);
}
}
private void DownloadPlugins()
{
var folders = Directory.GetDirectories(PluginDir);
@@ -80,7 +106,7 @@ namespace Torch.Managers
foreach (var repository in toDownload)
{
var manifest = new PluginManifest {Repository = repository, Version = "0.0"};
var manifest = new PluginManifest { Repository = repository, Version = "0.0" };
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
}
@@ -118,8 +144,6 @@ namespace Torch.Managers
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
plugin.StoragePath = Torch.Config.InstancePath;
Plugins.Add(plugin);
_commandManager.RegisterPluginCommands(plugin);
}
catch (Exception e)
{

View File

@@ -21,6 +21,12 @@ namespace Torch.Session
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
private TorchSession _currentSession;
/// <inheritdoc />
public event TorchSessionLoadDel SessionLoaded;
/// <inheritdoc />
public event TorchSessionLoadDel SessionUnloading;
/// <inheritdoc/>
public ITorchSession CurrentSession => _currentSession;
@@ -46,7 +52,7 @@ namespace Torch.Session
return _factories.Remove(factory);
}
private void SessionLoaded()
private void LoadSession()
{
if (_currentSession != null)
{
@@ -63,12 +69,14 @@ namespace Torch.Session
CurrentSession.Managers.AddManager(manager);
}
(CurrentSession as TorchSession)?.Attach();
SessionLoaded?.Invoke(_currentSession);
}
private void SessionUnloaded()
private void UnloadSession()
{
if (_currentSession == null)
return;
SessionUnloading?.Invoke(_currentSession);
_log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
_currentSession.Detach();
_currentSession = null;
@@ -77,8 +85,8 @@ namespace Torch.Session
/// <inheritdoc/>
public override void Attach()
{
MySession.AfterLoading += SessionLoaded;
MySession.OnUnloaded += SessionUnloaded;
MySession.AfterLoading += LoadSession;
MySession.OnUnloaded += UnloadSession;
}
/// <inheritdoc/>
@@ -86,8 +94,8 @@ namespace Torch.Session
{
_currentSession?.Detach();
_currentSession = null;
MySession.AfterLoading -= SessionLoaded;
MySession.OnUnloaded -= SessionUnloaded;
MySession.AfterLoading -= LoadSession;
MySession.OnUnloaded -= UnloadSession;
}
}
}

View File

@@ -273,26 +273,58 @@ namespace Torch
private void OnSessionLoading()
{
Log.Debug("Session loading");
try
{
SessionLoading?.Invoke();
}
catch (Exception e)
{
Log.Error(e);
throw;
}
}
private void OnSessionLoaded()
{
Log.Debug("Session loaded");
try
{
SessionLoaded?.Invoke();
}
catch (Exception e)
{
Log.Error(e);
throw;
}
}
private void OnSessionUnloading()
{
Log.Debug("Session unloading");
try
{
SessionUnloading?.Invoke();
}
catch (Exception e)
{
Log.Error(e);
throw;
}
}
private void OnSessionUnloaded()
{
Log.Debug("Session unloaded");
try
{
SessionUnloaded?.Invoke();
}
catch (Exception e)
{
Log.Error(e);
throw;
}
}
/// <summary>
/// Hook into the VRage plugin system for updates.