diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5931769..6e11ce7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,9 +1,9 @@
# Making a Pull Request
-* Fork this repository and make sure your local **master** branch is up to date with the main repository.
-* Create a new branch for your addition with an appropriate name, e.g. **add-restart-command**
+* Fork this repository and make sure your local **staging** branch is up to date with the main repository.
+* Create a new branch from the **staging** branch for your addition with an appropriate name, e.g. **add-restart-command**
* PRs work by submitting the *entire* branch, so this allows you to continue work without locking up your whole repository.
* Commit your changes to that branch, making sure that you **follow the code guidelines below**.
-* Submit your branch as a PR to be reviewed.
+* Submit your branch as a PR to be reviewed, with Torch's **staging** branch as the base.
## Naming Conventions
* Types: **PascalCase**
diff --git a/Jenkins/release.ps1 b/Jenkins/release.ps1
new file mode 100644
index 0000000..81e5e63
--- /dev/null
+++ b/Jenkins/release.ps1
@@ -0,0 +1,52 @@
+param([string] $ApiBase, [string]$tagName, [string]$authinfo, [string[]] $assetPaths)
+Add-Type -AssemblyName "System.Web"
+
+$headers = @{
+ Authorization = "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($authinfo))
+ Accept = "application/vnd.github.v3+json"
+}
+try
+{
+ Write-Output("Checking if release with tag " + $tagName + " already exists...")
+ $release = Invoke-RestMethod -Uri ($ApiBase+"releases/tags/$tagName") -Method "GET" -Headers $headers
+ Write-Output(" Using existing release " + $release.id + " at " + $release.html_url)
+} catch {
+ Write-Output(" Doesn't exist")
+ $rel_arg = @{
+ tag_name=$tagName
+ name="Generated $tagName"
+ body=""
+ draft=$TRUE
+ prerelease=$tagName.Contains("alpha") -or $tagName.Contains("beta")
+ }
+ Write-Output("Creating new release " + $tagName + "...")
+ $release = Invoke-RestMethod -Uri ($ApiBase+"releases") -Method "POST" -Headers $headers -Body (ConvertTo-Json($rel_arg))
+ Write-Output(" Created new release " + $tagName + " at " + $release.html_url)
+}
+
+$assetsApiBase = $release.assets_url
+Write-Output("Checking for existing assets...")
+$existingAssets = Invoke-RestMethod -Uri ($assetsApiBase) -Method "GET" -Headers $headers
+$assetLabels = ($assetPaths | ForEach-Object {[System.IO.Path]::GetFileName($_)})
+foreach ($asset in $existingAssets) {
+ if ($assetLabels -contains $asset.name) {
+ $uri = $asset.url
+ Write-Output(" Deleting old asset " + $asset.name + " (id " + $asset.id + "); URI=" + $uri)
+ $result = Invoke-RestMethod -Uri $uri -Method "DELETE" -Headers $headers
+ }
+}
+Write-Output("Uploading assets...")
+$uploadUrl = $release.upload_url.Substring(0, $release.upload_url.LastIndexOf('{'))
+foreach ($asset in $assetPaths) {
+ $assetName = [System.IO.Path]::GetFileName($asset)
+ $assetType = [System.Web.MimeMapping]::GetMimeMapping($asset)
+ $assetData = [System.IO.File]::ReadAllBytes($asset)
+ $headerExtra = $headers + @{
+ "Content-Type" = $assetType
+ Name = $assetName
+ }
+ $uri = $uploadUrl + "?name=" + $assetName
+ Write-Output(" Uploading " + $asset + " as " + $assetType + "; URI=" + $uri)
+ $result = Invoke-RestMethod -Uri $uri -Method "POST" -Headers $headerExtra -Body $assetData
+ Write-Output(" ID=" + $result.id + ", found at=" + $result.browser_download_url)
+}
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index 841e5bc..cec3b0b 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -54,4 +54,14 @@ node {
archiveArtifacts artifacts: 'bin/x64/Release/Torch*', caseSensitive: false, fingerprint: true, onlyIfSuccessful: true
}
+
+ gitVersion = bat(returnStdout: true, script: "@git describe --tags").trim()
+ gitSimpleVersion = bat(returnStdout: true, script: "@git describe --tags --abbrev=0").trim()
+ if (gitVersion == gitSimpleVersion) {
+ stage('Release') {
+ withCredentials([usernamePassword(credentialsId: 'torch-github', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
+ powershell "& ./Jenkins/release.ps1 \"https://api.github.com/repos/TorchAPI/Torch/\" \"$gitSimpleVersion\" \"$USERNAME:$PASSWORD\" @(\"bin/torch-server.zip\", \"bin/torch-client.zip\")"
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/Torch.API/IChatMessage.cs b/Torch.API/IChatMessage.cs
deleted file mode 100644
index e9dbc11..0000000
--- a/Torch.API/IChatMessage.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Torch.API
-{
- public interface IChatMessage
- {
- ///
- /// The time the message was created.
- ///
- DateTime Timestamp { get; }
-
- ///
- /// The SteamID of the message author.
- ///
- ulong SteamId { get; }
-
- ///
- /// The name of the message author.
- ///
- string Name { get; }
-
- ///
- /// The content of the message.
- ///
- string Message { get; }
- }
-}
diff --git a/Torch.API/Managers/IChatManagerClient.cs b/Torch.API/Managers/IChatManagerClient.cs
index a3a1f64..d9a0bba 100644
--- a/Torch.API/Managers/IChatManagerClient.cs
+++ b/Torch.API/Managers/IChatManagerClient.cs
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Sandbox.Engine.Multiplayer;
+using Sandbox.Game.Multiplayer;
using VRage.Network;
namespace Torch.API.Managers
@@ -12,6 +14,38 @@ namespace Torch.API.Managers
///
public struct TorchChatMessage
{
+ ///
+ /// Creates a new torch chat message with the given author and message.
+ ///
+ /// Author's name
+ /// Message
+ public TorchChatMessage(string author, string message)
+ {
+ Timestamp = DateTime.Now;
+ AuthorSteamId = null;
+ Author = author;
+ Message = message;
+ Font = "Blue";
+ }
+
+ ///
+ /// Creates a new torch chat message with the given author and message.
+ ///
+ /// Author's steam ID
+ /// Message
+ public TorchChatMessage(ulong authorSteamId, string message)
+ {
+ Timestamp = DateTime.Now;
+ AuthorSteamId = authorSteamId;
+ Author = MyMultiplayer.Static?.GetMemberName(authorSteamId) ?? "Player";
+ Message = message;
+ Font = "Blue";
+ }
+
+ ///
+ /// This message's timestamp.
+ ///
+ public DateTime Timestamp;
///
/// The author's steam ID, if available. Else, null.
///
diff --git a/Torch.API/Managers/IMultiplayerManagerBase.cs b/Torch.API/Managers/IMultiplayerManagerBase.cs
index 8b552e7..09b76c9 100644
--- a/Torch.API/Managers/IMultiplayerManagerBase.cs
+++ b/Torch.API/Managers/IMultiplayerManagerBase.cs
@@ -7,14 +7,7 @@ using VRage.Game.ModAPI;
namespace Torch.API.Managers
{
///
- /// Delegate for received messages.
- ///
- /// Message data.
- /// Flag to broadcast message to other players.
- public delegate void MessageReceivedDel(IChatMessage message, ref bool sendToOthers);
-
- ///
- /// API for multiplayer related functions.
+ /// API for multiplayer related functions common to servers and clients.
///
public interface IMultiplayerManagerBase : IManager
{
diff --git a/Torch.API/Managers/IMultiplayerManagerServer.cs b/Torch.API/Managers/IMultiplayerManagerServer.cs
index 36dddf8..b0247ba 100644
--- a/Torch.API/Managers/IMultiplayerManagerServer.cs
+++ b/Torch.API/Managers/IMultiplayerManagerServer.cs
@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Torch.API.Managers
{
+ ///
+ /// API for multiplayer functions that exist on servers and lobbies
+ ///
public interface IMultiplayerManagerServer : IMultiplayerManagerBase
{
///
diff --git a/Torch.API/Session/ITorchSession.cs b/Torch.API/Session/ITorchSession.cs
index 710c9dd..59add06 100644
--- a/Torch.API/Session/ITorchSession.cs
+++ b/Torch.API/Session/ITorchSession.cs
@@ -25,5 +25,15 @@ namespace Torch.API.Session
///
IDependencyManager Managers { get; }
+
+ ///
+ /// The current state of the session
+ ///
+ TorchSessionState State { get; }
+
+ ///
+ /// Event raised when the changes.
+ ///
+ event TorchSessionStateChangedDel StateChanged;
}
}
diff --git a/Torch.API/Session/ITorchSessionManager.cs b/Torch.API/Session/ITorchSessionManager.cs
index 8e5a5ed..bfa3b88 100644
--- a/Torch.API/Session/ITorchSessionManager.cs
+++ b/Torch.API/Session/ITorchSessionManager.cs
@@ -17,32 +17,21 @@ namespace Torch.API.Session
/// The manager that will live in the session, or null if none.
public delegate IManager SessionManagerFactoryDel(ITorchSession session);
- ///
- /// Fired when the given session has been completely loaded or is unloading.
- ///
- /// The session
- public delegate void TorchSessionLoadDel(ITorchSession session);
-
///
/// Manages the creation and destruction of instances for each created by Space Engineers.
///
public interface ITorchSessionManager : IManager
{
- ///
- /// Fired when a has finished loading.
- ///
- event TorchSessionLoadDel SessionLoaded;
-
- ///
- /// Fired when a has begun unloading.
- ///
- event TorchSessionLoadDel SessionUnloading;
-
///
/// The currently running session
///
ITorchSession CurrentSession { get; }
+ ///
+ /// Raised when any changes.
+ ///
+ event TorchSessionStateChangedDel SessionStateChanged;
+
///
/// Adds the given factory as a supplier for session based managers
///
diff --git a/Torch.API/Session/TorchSessionState.cs b/Torch.API/Session/TorchSessionState.cs
new file mode 100644
index 0000000..6d02da3
--- /dev/null
+++ b/Torch.API/Session/TorchSessionState.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Torch.API.Session
+{
+ ///
+ /// Represents the state of a
+ ///
+ public enum TorchSessionState
+ {
+ ///
+ /// The session has been created, and is now loading.
+ ///
+ Loading,
+ ///
+ /// The session has loaded, and is now running.
+ ///
+ Loaded,
+ ///
+ /// The session was running, and is now unloading.
+ ///
+ Unloading,
+ ///
+ /// The session was unloading, and is now unloaded and stopped.
+ ///
+ Unloaded
+ }
+
+ ///
+ /// Callback raised when a session's state changes
+ ///
+ /// The session who had a state change
+ /// The session's new state
+ public delegate void TorchSessionStateChangedDel(ITorchSession session, TorchSessionState newState);
+}
diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj
index 6ffcc86..d771abd 100644
--- a/Torch.API/Torch.API.csproj
+++ b/Torch.API/Torch.API.csproj
@@ -160,7 +160,6 @@
Properties\AssemblyVersion.cs
-
@@ -186,6 +185,7 @@
+
diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs
index 933d683..e4fe99e 100644
--- a/Torch.Server/Initializer.cs
+++ b/Torch.Server/Initializer.cs
@@ -8,6 +8,7 @@ using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Windows.Threading;
using NLog;
using Torch.Utils;
@@ -84,15 +85,15 @@ quit";
_server = new TorchServer(_config);
_server.Init();
- if (_config.NoGui || _config.Autostart)
- {
- new Thread(_server.Start).Start();
- }
-
if (!_config.NoGui)
{
- new TorchUI(_server).ShowDialog();
+ var ui = new TorchUI(_server);
+ if (_config.Autostart)
+ new Thread(_server.Start).Start();
+ ui.ShowDialog();
}
+ else
+ _server.Start();
_resolver?.Dispose();
}
diff --git a/Torch.Server/Managers/InstanceManager.cs b/Torch.Server/Managers/InstanceManager.cs
index c8d52b8..f11a446 100644
--- a/Torch.Server/Managers/InstanceManager.cs
+++ b/Torch.Server/Managers/InstanceManager.cs
@@ -131,7 +131,7 @@ namespace Torch.Server.Managers
public void SaveConfig()
{
- DedicatedConfig.Save();
+ DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
Log.Info("Saved dedicated config.");
try
diff --git a/Torch.Server/Views/ChatControl.xaml b/Torch.Server/Views/ChatControl.xaml
index 8d3e692..3f6ea01 100644
--- a/Torch.Server/Views/ChatControl.xaml
+++ b/Torch.Server/Views/ChatControl.xaml
@@ -10,20 +10,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/Torch.Server/Views/ChatControl.xaml.cs b/Torch.Server/Views/ChatControl.xaml.cs
index 1c3152e..59334b2 100644
--- a/Torch.Server/Views/ChatControl.xaml.cs
+++ b/Torch.Server/Views/ChatControl.xaml.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
+using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
@@ -24,6 +25,7 @@ using Torch.API.Managers;
using Torch.API.Session;
using Torch.Managers;
using Torch.Server.Managers;
+using VRage.Game;
namespace Torch.Server
{
@@ -42,43 +44,75 @@ namespace Torch.Server
public void BindServer(ITorchServer server)
{
_server = (TorchBase)server;
- ChatItems.Items.Clear();
+ Dispatcher.Invoke(() =>
+ {
+ ChatItems.Inlines.Clear();
+ });
var sessionManager = server.Managers.GetManager();
- sessionManager.SessionLoaded += BindSession;
- sessionManager.SessionUnloading += UnbindSession;
+ if (sessionManager != null)
+ sessionManager.SessionStateChanged += SessionStateChanged;
}
- private void BindSession(ITorchSession session)
+ private void SessionStateChanged(ITorchSession session, TorchSessionState state)
{
- Dispatcher.Invoke(() =>
+ switch (state)
{
- var chatMgr = _server?.CurrentSession?.Managers.GetManager();
- if (chatMgr != null)
- DataContext = new ChatManagerProxy(chatMgr);
- });
+ case TorchSessionState.Loading:
+ Dispatcher.Invoke(() => ChatItems.Inlines.Clear());
+ break;
+ case TorchSessionState.Loaded:
+ {
+ var chatMgr = session.Managers.GetManager();
+ if (chatMgr != null)
+ chatMgr.MessageRecieved += OnMessageRecieved;
+ }
+ break;
+ case TorchSessionState.Unloading:
+ {
+ var chatMgr = session.Managers.GetManager();
+ if (chatMgr != null)
+ chatMgr.MessageRecieved -= OnMessageRecieved;
+ }
+ break;
+ case TorchSessionState.Unloaded:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(state), state, null);
+ }
}
- private void UnbindSession(ITorchSession session)
+ private void OnMessageRecieved(TorchChatMessage msg, ref bool consumed)
{
- Dispatcher.Invoke(() =>
- {
- (DataContext as ChatManagerProxy)?.Dispose();
- DataContext = null;
- });
+ InsertMessage(msg);
}
- private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ private static readonly Dictionary _brushes = new Dictionary();
+ private static Brush LookupBrush(string font)
{
- ChatItems.ScrollToItem(ChatItems.Items.Count - 1);
- /*
- if (VisualTreeHelper.GetChildrenCount(ChatItems) > 0)
+ if (_brushes.TryGetValue(font, out Brush result))
+ return result;
+ Brush brush = typeof(Brushes).GetField(font, BindingFlags.Static)?.GetValue(null) as Brush ?? Brushes.Blue;
+ _brushes.Add(font, brush);
+ return brush;
+ }
+
+ private void InsertMessage(TorchChatMessage msg)
+ {
+ if (Dispatcher.CheckAccess())
{
-
- Border border = (Border)VisualTreeHelper.GetChild(ChatItems, 0);
- ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
- scrollViewer.ScrollToBottom();
- }*/
+ bool atBottom = ChatScroller.VerticalOffset + 8 > ChatScroller.ScrollableHeight;
+ var span = new Span();
+ span.Inlines.Add($"{msg.Timestamp} ");
+ span.Inlines.Add(new Run(msg.Author) { Foreground = LookupBrush(msg.Font) });
+ span.Inlines.Add($": {msg.Message}");
+ span.Inlines.Add(new LineBreak());
+ ChatItems.Inlines.Add(span);
+ if (atBottom)
+ ChatScroller.ScrollToBottom();
+ }
+ else
+ Dispatcher.Invoke(() => InsertMessage(msg));
}
private void SendButton_Click(object sender, RoutedEventArgs e)
@@ -102,11 +136,12 @@ namespace Torch.Server
var commands = _server.CurrentSession?.Managers.GetManager();
if (commands != null && commands.IsCommand(text))
{
- (DataContext as ChatManagerProxy)?.AddMessage(new TorchChatMessage() { Author = "Server", Message = text });
+ InsertMessage(new TorchChatMessage("Server", text) { Font = MyFontEnum.DarkBlue });
_server.Invoke(() =>
{
- var response = commands.HandleCommandFromServer(text);
- Dispatcher.BeginInvoke(() => OnMessageEntered_Callback(response));
+ string response = commands.HandleCommandFromServer(text);
+ if (!string.IsNullOrWhiteSpace(response))
+ InsertMessage(new TorchChatMessage("Server", response) { Font = MyFontEnum.Blue });
});
}
else
@@ -115,40 +150,5 @@ namespace Torch.Server
}
Message.Text = "";
}
-
- private void OnMessageEntered_Callback(string 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 ChatHistory { get; } = new ObservableList();
-
- ///
- 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));
- }
- }
}
}
diff --git a/Torch.Server/Views/PlayerListControl.xaml.cs b/Torch.Server/Views/PlayerListControl.xaml.cs
index b76b7c7..c9e9676 100644
--- a/Torch.Server/Views/PlayerListControl.xaml.cs
+++ b/Torch.Server/Views/PlayerListControl.xaml.cs
@@ -46,18 +46,20 @@ namespace Torch.Server
_server = server;
var sessionManager = server.Managers.GetManager();
- sessionManager.SessionLoaded += BindSession;
- sessionManager.SessionUnloading += UnbindSession;
+ sessionManager.SessionStateChanged += SessionStateChanged;
}
- private void BindSession(ITorchSession session)
+ private void SessionStateChanged(ITorchSession session, TorchSessionState newState)
{
- Dispatcher.Invoke(() => DataContext = _server?.CurrentSession?.Managers.GetManager());
- }
-
- private void UnbindSession(ITorchSession session)
- {
- Dispatcher.Invoke(() => DataContext = null);
+ switch (newState)
+ {
+ case TorchSessionState.Loaded:
+ Dispatcher.Invoke(() => DataContext = _server?.CurrentSession?.Managers.GetManager());
+ break;
+ case TorchSessionState.Unloading:
+ Dispatcher.Invoke(() => DataContext = null);
+ break;
+ }
}
private void KickButton_Click(object sender, RoutedEventArgs e)
@@ -68,7 +70,7 @@ namespace Torch.Server
private void BanButton_Click(object sender, RoutedEventArgs e)
{
- var player = (KeyValuePair) PlayerList.SelectedItem;
+ var player = (KeyValuePair)PlayerList.SelectedItem;
_server.CurrentSession?.Managers.GetManager()?.BanPlayer(player.Key);
}
}
diff --git a/Torch.Tests/PatchTest.cs b/Torch.Tests/PatchTest.cs
new file mode 100644
index 0000000..1ee4225
--- /dev/null
+++ b/Torch.Tests/PatchTest.cs
@@ -0,0 +1,386 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Torch.Managers.PatchManager;
+using Torch.Managers.PatchManager.MSIL;
+using Torch.Utils;
+using Xunit;
+
+// ReSharper disable UnusedMember.Local
+namespace Torch.Tests
+{
+#pragma warning disable 414
+ public class PatchTest
+ {
+ #region TestRunner
+ private static readonly PatchManager _patchContext = new PatchManager(null);
+
+ [Theory]
+ [MemberData(nameof(Prefixes))]
+ public void TestPrefix(TestBootstrap runner)
+ {
+ runner.TestPrefix();
+ }
+
+ [Theory]
+ [MemberData(nameof(Transpilers))]
+ public void TestTranspile(TestBootstrap runner)
+ {
+ runner.TestTranspile();
+ }
+
+ [Theory]
+ [MemberData(nameof(Suffixes))]
+ public void TestSuffix(TestBootstrap runner)
+ {
+ runner.TestSuffix();
+ }
+
+ [Theory]
+ [MemberData(nameof(Combo))]
+ public void TestCombo(TestBootstrap runner)
+ {
+ runner.TestCombo();
+ }
+
+
+
+ public class TestBootstrap
+ {
+ public bool HasPrefix => _prefixMethod != null;
+ public bool HasTranspile => _transpileMethod != null;
+ public bool HasSuffix => _suffixMethod != null;
+
+ private readonly MethodInfo _prefixMethod, _prefixAssert;
+ private readonly MethodInfo _suffixMethod, _suffixAssert;
+ private readonly MethodInfo _transpileMethod, _transpileAssert;
+ private readonly MethodInfo _targetMethod, _targetAssert;
+ private readonly MethodInfo _resetMethod;
+ private readonly object _instance;
+ private readonly object[] _targetParams;
+ private readonly Type _type;
+
+ public TestBootstrap(Type t)
+ {
+ const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
+ _type = t;
+ _prefixMethod = t.GetMethod("Prefix", flags);
+ _prefixAssert = t.GetMethod("AssertPrefix", flags);
+ _suffixMethod = t.GetMethod("Suffix", flags);
+ _suffixAssert = t.GetMethod("AssertSuffix", flags);
+ _transpileMethod = t.GetMethod("Transpile", flags);
+ _transpileAssert = t.GetMethod("AssertTranspile", flags);
+ _targetMethod = t.GetMethod("Target", flags);
+ _targetAssert = t.GetMethod("AssertNormal", flags);
+ _resetMethod = t.GetMethod("Reset", flags);
+ if (_targetMethod == null)
+ throw new Exception($"{t.FullName} must have a method named Target");
+ if (_targetAssert == null)
+ throw new Exception($"{t.FullName} must have a method named AssertNormal");
+ _instance = !_targetMethod.IsStatic ? Activator.CreateInstance(t) : null;
+ _targetParams = (object[])t.GetField("_targetParams", flags)?.GetValue(null) ?? new object[0];
+ }
+
+ private void Invoke(MethodBase i, params object[] args)
+ {
+ if (i == null) return;
+ i.Invoke(i.IsStatic ? null : _instance, args);
+ }
+
+ private void Invoke()
+ {
+ _targetMethod.Invoke(_instance, _targetParams);
+ Invoke(_targetAssert);
+ }
+
+ public void TestPrefix()
+ {
+ Invoke(_resetMethod);
+ PatchContext context = _patchContext.AcquireContext();
+ context.GetPattern(_targetMethod).Prefixes.Add(_prefixMethod);
+ _patchContext.Commit();
+
+ Invoke();
+ Invoke(_prefixAssert);
+
+ _patchContext.FreeContext(context);
+ _patchContext.Commit();
+ }
+
+ public void TestSuffix()
+ {
+ Invoke(_resetMethod);
+ PatchContext context = _patchContext.AcquireContext();
+ context.GetPattern(_targetMethod).Suffixes.Add(_suffixMethod);
+ _patchContext.Commit();
+
+ Invoke();
+ Invoke(_suffixAssert);
+
+ _patchContext.FreeContext(context);
+ _patchContext.Commit();
+ }
+
+ public void TestTranspile()
+ {
+ Invoke(_resetMethod);
+ PatchContext context = _patchContext.AcquireContext();
+ context.GetPattern(_targetMethod).Transpilers.Add(_transpileMethod);
+ _patchContext.Commit();
+
+ Invoke();
+ Invoke(_transpileAssert);
+
+ _patchContext.FreeContext(context);
+ _patchContext.Commit();
+ }
+
+ public void TestCombo()
+ {
+ Invoke(_resetMethod);
+ PatchContext context = _patchContext.AcquireContext();
+ if (_prefixMethod != null)
+ context.GetPattern(_targetMethod).Prefixes.Add(_prefixMethod);
+ if (_transpileMethod != null)
+ context.GetPattern(_targetMethod).Transpilers.Add(_transpileMethod);
+ if (_suffixMethod != null)
+ context.GetPattern(_targetMethod).Suffixes.Add(_suffixMethod);
+ _patchContext.Commit();
+
+ Invoke();
+ Invoke(_prefixAssert);
+ Invoke(_transpileAssert);
+ Invoke(_suffixAssert);
+
+ _patchContext.FreeContext(context);
+ _patchContext.Commit();
+ }
+
+ public override string ToString()
+ {
+ return _type.Name;
+ }
+ }
+
+ private class PatchTestAttribute : Attribute
+ {
+ }
+
+ private static readonly List _patchTest;
+
+ static PatchTest()
+ {
+ TestUtils.Init();
+ foreach (Type type in typeof(PatchManager).Assembly.GetTypes())
+ if (type.Namespace?.StartsWith(typeof(PatchManager).Namespace ?? "") ?? false)
+ ReflectedManager.Process(type);
+
+ _patchTest = new List();
+ foreach (Type type in typeof(PatchTest).GetNestedTypes(BindingFlags.NonPublic))
+ if (type.GetCustomAttribute(typeof(PatchTestAttribute)) != null)
+ _patchTest.Add(new TestBootstrap(type));
+ }
+
+ public static IEnumerable
public void UpdatePlugins()
{
- if (_sessionManager != null)
- {
- _sessionManager.SessionLoaded += AttachCommandsToSession;
- _sessionManager.SessionUnloading += DetachCommandsFromSession;
- }
foreach (var plugin in Plugins)
plugin.Update();
}
+ private Action _attachCommandsHandler = null;
+
+ private void SessionStateChanged(ITorchSession session, TorchSessionState newState)
+ {
+ var cmdManager = session.Managers.GetManager();
+ if (cmdManager == null)
+ return;
+ switch (newState)
+ {
+ case TorchSessionState.Loaded:
+ if (_attachCommandsHandler != null)
+ PluginLoaded -= _attachCommandsHandler;
+ _attachCommandsHandler = (x) => cmdManager.RegisterPluginCommands(x);
+ PluginLoaded += _attachCommandsHandler;
+ foreach (ITorchPlugin plugin in Plugins)
+ cmdManager.RegisterPluginCommands(plugin);
+ break;
+ case TorchSessionState.Unloading:
+ if (_attachCommandsHandler != null)
+ {
+ PluginLoaded -= _attachCommandsHandler;
+ _attachCommandsHandler = null;
+ }
+ foreach (ITorchPlugin plugin in Plugins)
+ {
+ // cmdMgr?.UnregisterPluginCommands(plugin);
+ }
+ break;
+ case TorchSessionState.Loading:
+ case TorchSessionState.Unloaded:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(newState), newState, null);
+ }
+ }
+
+ ///
+ /// Prepares the plugin manager for loading.
+ ///
+ public override void Attach()
+ {
+ if (_sessionManager != null)
+ _sessionManager.SessionStateChanged += SessionStateChanged;
+ }
+
///
/// Unloads all plugins.
///
public override void Detach()
{
if (_sessionManager != null)
- {
- _sessionManager.SessionLoaded -= AttachCommandsToSession;
- _sessionManager.SessionUnloading -= DetachCommandsFromSession;
- }
+ _sessionManager.SessionStateChanged -= SessionStateChanged;
foreach (var plugin in Plugins)
plugin.Dispose();
Plugins.Clear();
}
- private void AttachCommandsToSession(ITorchSession session)
- {
- var cmdMgr = session.Managers.GetManager();
- foreach (ITorchPlugin plugin in Plugins)
- cmdMgr?.RegisterPluginCommands(plugin);
- }
-
- private void DetachCommandsFromSession(ITorchSession session)
- {
- var cmdMgr = session.Managers.GetManager();
- foreach (ITorchPlugin plugin in Plugins) {
- // cmdMgr?.UnregisterPluginCommands(plugin);
- }
- }
-
private void DownloadPlugins()
{
var folders = Directory.GetDirectories(PluginDir);
@@ -144,6 +167,7 @@ namespace Torch.Managers
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
plugin.StoragePath = Torch.Config.InstancePath;
Plugins.Add(plugin);
+ PluginLoaded?.Invoke(plugin);
}
catch (Exception e)
{
diff --git a/Torch/Session/TorchSession.cs b/Torch/Session/TorchSession.cs
index 6ec4a2d..2120ef1 100644
--- a/Torch/Session/TorchSession.cs
+++ b/Torch/Session/TorchSession.cs
@@ -45,5 +45,20 @@ namespace Torch.Session
{
Managers.Detach();
}
+
+ private TorchSessionState _state = TorchSessionState.Loading;
+ ///
+ public TorchSessionState State
+ {
+ get => _state;
+ internal set
+ {
+ _state = value;
+ StateChanged?.Invoke(this, _state);
+ }
+ }
+
+ ///
+ public event TorchSessionStateChangedDel StateChanged;
}
}
diff --git a/Torch/Session/TorchSessionManager.cs b/Torch/Session/TorchSessionManager.cs
index 933f61f..426b320 100644
--- a/Torch/Session/TorchSessionManager.cs
+++ b/Torch/Session/TorchSessionManager.cs
@@ -22,10 +22,7 @@ namespace Torch.Session
private TorchSession _currentSession;
///
- public event TorchSessionLoadDel SessionLoaded;
-
- ///
- public event TorchSessionLoadDel SessionUnloading;
+ public event TorchSessionStateChangedDel SessionStateChanged;
///
public ITorchSession CurrentSession => _currentSession;
@@ -52,50 +49,130 @@ namespace Torch.Session
return _factories.Remove(factory);
}
- private void LoadSession()
- {
- if (_currentSession != null)
- {
- _log.Warn($"Override old torch session {_currentSession.KeenSession.Name}");
- _currentSession.Detach();
- }
+ #region Session events
- _log.Info($"Starting new torch session for {MySession.Static.Name}");
- _currentSession = new TorchSession(Torch, MySession.Static);
- foreach (SessionManagerFactoryDel factory in _factories)
- {
- IManager manager = factory(CurrentSession);
- if (manager != null)
- CurrentSession.Managers.AddManager(manager);
- }
- (CurrentSession as TorchSession)?.Attach();
- SessionLoaded?.Invoke(_currentSession);
- }
-
- private void UnloadSession()
+ private void SetState(TorchSessionState state)
{
if (_currentSession == null)
return;
- SessionUnloading?.Invoke(_currentSession);
- _log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
- _currentSession.Detach();
- _currentSession = null;
+ _currentSession.State = state;
+ SessionStateChanged?.Invoke(_currentSession, _currentSession.State);
}
+ private void SessionLoading()
+ {
+ try
+ {
+ if (_currentSession != null)
+ {
+ _log.Warn($"Override old torch session {_currentSession.KeenSession.Name}");
+ _currentSession.Detach();
+ }
+
+ _log.Info($"Starting new torch session for {MySession.Static.Name}");
+
+ _currentSession = new TorchSession(Torch, MySession.Static);
+ SetState(TorchSessionState.Loading);
+ }
+ catch (Exception e)
+ {
+ _log.Error(e);
+ throw;
+ }
+ }
+
+ private void SessionLoaded()
+ {
+ try
+ {
+ if (_currentSession == null)
+ {
+ _log.Warn("Session loaded event occurred when we don't have a session.");
+ return;
+ }
+ foreach (SessionManagerFactoryDel factory in _factories)
+ {
+ IManager manager = factory(CurrentSession);
+ if (manager != null)
+ CurrentSession.Managers.AddManager(manager);
+ }
+ (CurrentSession as TorchSession)?.Attach();
+ SetState(TorchSessionState.Loaded);
+ }
+ catch (Exception e)
+ {
+ _log.Error(e);
+ throw;
+ }
+ }
+
+ private void SessionUnloading()
+ {
+ try
+ {
+ if (_currentSession == null)
+ {
+ _log.Warn("Session unloading event occurred when we don't have a session.");
+ return;
+ }
+ SetState(TorchSessionState.Unloading);
+ }
+ catch (Exception e)
+ {
+ _log.Error(e);
+ throw;
+ }
+ }
+
+ private void SessionUnloaded()
+ {
+ try
+ {
+ if (_currentSession == null)
+ {
+ _log.Warn("Session unloading event occurred when we don't have a session.");
+ return;
+ }
+ _log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
+ SetState(TorchSessionState.Unloaded);
+ _currentSession.Detach();
+ _currentSession = null;
+ }
+ catch (Exception e)
+ {
+ _log.Error(e);
+ throw;
+ }
+ }
+ #endregion
+
///
public override void Attach()
{
- MySession.AfterLoading += LoadSession;
- MySession.OnUnloaded += UnloadSession;
+ MySession.OnLoading += SessionLoading;
+ MySession.AfterLoading += SessionLoaded;
+ MySession.OnUnloading += SessionUnloading;
+ MySession.OnUnloaded += SessionUnloaded;
}
+
///
public override void Detach()
{
- _currentSession?.Detach();
- _currentSession = null;
- MySession.AfterLoading -= LoadSession;
- MySession.OnUnloaded -= UnloadSession;
+ MySession.OnLoading -= SessionLoading;
+ MySession.AfterLoading -= SessionLoaded;
+ MySession.OnUnloading -= SessionUnloading;
+ MySession.OnUnloaded -= SessionUnloaded;
+
+ if (_currentSession != null)
+ {
+ if (_currentSession.State == TorchSessionState.Loaded)
+ SetState(TorchSessionState.Unloading);
+ if (_currentSession.State == TorchSessionState.Unloading)
+ SetState(TorchSessionState.Unloaded);
+ _currentSession.Detach();
+ _currentSession = null;
+ }
}
}
}
diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj
index 4b7be00..aed1db0 100644
--- a/Torch/Torch.csproj
+++ b/Torch/Torch.csproj
@@ -21,6 +21,7 @@
x64
prompt
MinimumRecommendedRules.ruleset
+ true
$(SolutionDir)\bin\x64\Release\
@@ -31,6 +32,7 @@
prompt
MinimumRecommendedRules.ruleset
$(SolutionDir)\bin\x64\Release\Torch.xml
+ true
@@ -152,12 +154,28 @@
Properties\AssemblyVersion.cs
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Torch/Utils/ReflectedManager.cs b/Torch/Utils/ReflectedManager.cs
index 2f52507..39dae0b 100644
--- a/Torch/Utils/ReflectedManager.cs
+++ b/Torch/Utils/ReflectedManager.cs
@@ -23,6 +23,15 @@ namespace Torch.Utils
/// Declaring type of the member to access. If null, inferred from the instance argument type.
///
public Type Type { get; set; } = null;
+
+ ///
+ /// Assembly qualified name of
+ ///
+ public string TypeName
+ {
+ get => Type?.AssemblyQualifiedName;
+ set => Type = value == null ? null : Type.GetType(value, true);
+ }
}
#region MemberInfoAttributes
@@ -160,6 +169,19 @@ namespace Torch.Utils
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedMethodAttribute : ReflectedMemberAttribute
{
+ ///
+ /// When set the parameters types for the method are assumed to be this.
+ ///
+ public Type[] OverrideTypes { get; set; }
+
+ ///
+ /// Assembly qualified names of
+ ///
+ public string[] OverrideTypeNames
+ {
+ get => OverrideTypes.Select(x => x.AssemblyQualifiedName).ToArray();
+ set => OverrideTypes = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
+ }
}
///
@@ -534,10 +556,14 @@ namespace Torch.Utils
trueParameterTypes = parameters.Skip(1).Select(x => x.ParameterType).ToArray();
}
+ var invokeTypes = new Type[trueParameterTypes.Length];
+ for (var i = 0; i < invokeTypes.Length; i++)
+ invokeTypes[i] = attr.OverrideTypes?[i] ?? trueParameterTypes[i];
+
MethodInfo methodInstance = trueType.GetMethod(attr.Name ?? field.Name,
(attr is ReflectedStaticMethodAttribute ? BindingFlags.Static : BindingFlags.Instance) |
BindingFlags.Public |
- BindingFlags.NonPublic, null, CallingConventions.Any, trueParameterTypes, null);
+ BindingFlags.NonPublic, null, CallingConventions.Any, invokeTypes, null);
if (methodInstance == null)
{
string methodType = attr is ReflectedStaticMethodAttribute ? "static" : "instance";
@@ -547,13 +573,38 @@ namespace Torch.Utils
$"Unable to find {methodType} method {attr.Name ?? field.Name} in type {trueType.FullName} with parameters {methodParams}");
}
+
if (attr is ReflectedStaticMethodAttribute)
- field.SetValue(null, Delegate.CreateDelegate(field.FieldType, methodInstance));
+ {
+ if (attr.OverrideTypes != null)
+ {
+ ParameterExpression[] paramExp =
+ parameters.Select(x => Expression.Parameter(x.ParameterType)).ToArray();
+ var argExp = new Expression[invokeTypes.Length];
+ for (var i = 0; i < argExp.Length; i++)
+ if (invokeTypes[i] != paramExp[i].Type)
+ argExp[i] = Expression.Convert(paramExp[i], invokeTypes[i]);
+ else
+ argExp[i] = paramExp[i];
+ field.SetValue(null,
+ Expression.Lambda(Expression.Call(methodInstance, argExp), paramExp)
+ .Compile());
+ }
+ else
+ field.SetValue(null, Delegate.CreateDelegate(field.FieldType, methodInstance));
+ }
else
{
- ParameterExpression[] paramExp = parameters.Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToArray();
+ ParameterExpression[] paramExp =
+ parameters.Select(x => Expression.Parameter(x.ParameterType)).ToArray();
+ var argExp = new Expression[invokeTypes.Length];
+ for (var i = 0; i < argExp.Length; i++)
+ if (invokeTypes[i] != paramExp[i + 1].Type)
+ argExp[i] = Expression.Convert(paramExp[i + 1], invokeTypes[i]);
+ else
+ argExp[i] = paramExp[i + 1];
field.SetValue(null,
- Expression.Lambda(Expression.Call(paramExp[0], methodInstance, paramExp.Skip(1)), paramExp)
+ Expression.Lambda(Expression.Call(paramExp[0], methodInstance, argExp), paramExp)
.Compile());
}
}
@@ -589,7 +640,7 @@ namespace Torch.Utils
if (trueType == null && isStatic)
throw new ArgumentException("Static field setters need their type defined", nameof(field));
- if (!isStatic)
+ if (!isStatic && trueType == null)
trueType = parameters[0].ParameterType;
}
else if (attr is ReflectedGetterAttribute)
@@ -602,7 +653,7 @@ namespace Torch.Utils
if (trueType == null && isStatic)
throw new ArgumentException("Static field getters need their type defined", nameof(field));
- if (!isStatic)
+ if (!isStatic && trueType == null)
trueType = parameters[0].ParameterType;
}
else
@@ -620,10 +671,15 @@ namespace Torch.Utils
$"Unable to find field or property for {trueName} in {trueType.FullName} or its base types", nameof(field));
ParameterExpression[] paramExp = parameters.Select(x => Expression.Parameter(x.ParameterType)).ToArray();
+ Expression instanceExpr = null;
+ if (!isStatic)
+ {
+ instanceExpr = trueType == paramExp[0].Type ? (Expression) paramExp[0] : Expression.Convert(paramExp[0], trueType);
+ }
MemberExpression fieldExp = sourceField != null
- ? Expression.Field(isStatic ? null : paramExp[0], sourceField)
- : Expression.Property(isStatic ? null : paramExp[0], sourceProperty);
+ ? Expression.Field(instanceExpr, sourceField)
+ : Expression.Property(instanceExpr, sourceProperty);
Expression impl;
if (attr is ReflectedSetterAttribute)
{