From 48b212faafffaded16a2e0407e24ef4aba01f837 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Sun, 20 Aug 2017 21:48:42 -0700 Subject: [PATCH] Reflection unit testing Jenkins integration (we can only hope) --- Jenkinsfile | 17 + Torch.Client/Torch.Client.csproj | 1 - Torch.Tests/Properties/AssemblyInfo.cs | 36 +++ Torch.Tests/ReflectionTests.cs | 306 ++++++++++++++++++ Torch.Tests/Torch.Tests.csproj | 76 +++++ Torch.Tests/packages.config | 10 + Torch.sln | 23 +- Torch/Managers/MultiplayerManager.cs | 102 +++--- Torch/Managers/ReflectionManager.cs | 68 ++-- Torch/Torch.csproj | 1 + .../TorchAssemblyResolver.cs | 2 +- 11 files changed, 547 insertions(+), 95 deletions(-) create mode 100644 Torch.Tests/Properties/AssemblyInfo.cs create mode 100644 Torch.Tests/ReflectionTests.cs create mode 100644 Torch.Tests/Torch.Tests.csproj create mode 100644 Torch.Tests/packages.config rename {Torch.Client => Torch}/TorchAssemblyResolver.cs (97%) diff --git a/Jenkinsfile b/Jenkinsfile index 2979f70..82d7851 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -17,6 +17,23 @@ node { bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=Release /p:Platform=x64" } + stage('Test') { + bat "\"packages/xunit.runner.console.2.2.0/tools/xunit.console.exe\" \"bin-test/x64/Release/Torch.Tests.dll\" -parallel none -nunit \"reports/Torch.Tests.xml\"" + step([ + $class: 'XUnitBuilder', + thresholdMode: 1, + thresholds: [[$class: 'FailedThreshold', failureThreshold: '1']], + tools: [[ + $class: 'XUnitDotNetTestType', + deleteOutputFiles: true, + failIfNotNew: true, + pattern: 'reports/*.xml', + skipNoTestFiles: false, + stopProcessingIfError: true + ]] + ]) + } + stage('Archive') { archive 'bin/x64/Release/Torch.*' } diff --git a/Torch.Client/Torch.Client.csproj b/Torch.Client/Torch.Client.csproj index d6054c8..77e51d5 100644 --- a/Torch.Client/Torch.Client.csproj +++ b/Torch.Client/Torch.Client.csproj @@ -122,7 +122,6 @@ True - diff --git a/Torch.Tests/Properties/AssemblyInfo.cs b/Torch.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..089d989 --- /dev/null +++ b/Torch.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Torch.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Torch.Tests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c3c8b671-6ad1-44aa-a8da-e0c0dc0fedf5")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Torch.Tests/ReflectionTests.cs b/Torch.Tests/ReflectionTests.cs new file mode 100644 index 0000000..eec6ab7 --- /dev/null +++ b/Torch.Tests/ReflectionTests.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Torch.API; +using Torch.Client; +using Torch.Managers; +using Xunit; +using Xunit.Abstractions; + +namespace Torch.Tests +{ + public class ReflectionTests + { + private static string GetGameBinaries() + { + string dir = Environment.CurrentDirectory; + while (!string.IsNullOrWhiteSpace(dir)) + { + string gameBin = Path.Combine(dir, "GameBinaries"); + if (Directory.Exists(gameBin)) + return gameBin; + + dir = Path.GetDirectoryName(dir); + } + throw new Exception("GetGameBinaries failed to find a folder named GameBinaries in the directory tree"); + } + + private static readonly TorchAssemblyResolver _torchResolver = + new TorchAssemblyResolver(GetGameBinaries()); + + #region Binding + [Theory] + [MemberData(nameof(Getters))] + public void TestBindingGetter(FieldRef field) + { + Assert.True(ReflectionManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } + + [Theory] + [MemberData(nameof(Setters))] + public void TestBindingSetter(FieldRef field) + { + Assert.True(ReflectionManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } + + [Theory] + [MemberData(nameof(Invokers))] + public void TestBindingInvoker(FieldRef field) + { + Assert.True(ReflectionManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } + #endregion + + #region Results + #region Dummy + private class ReflectionTestTarget + { + public int TestField; + public int TestProperty { get; set; } + + /// + /// Return true when greater or equal than 0 + /// + public bool TestCall(int k) + { + return k >= 0; + } + + public static int TestFieldStatic; + public static int TestPropertyStatic { get; set; } + + /// + /// Return true when greater or equal than 0 + /// + public static bool TestCallStatic(int k) + { + return k >= 0; + } + } + + private class ReflectionTestBinding + { + [ReflectedGetter(Name = "TestField")] + public static Func TestFieldGetter; + [ReflectedSetter(Name = "TestField")] + public static Action TestFieldSetter; + + [ReflectedGetter(Name = "TestProperty")] + public static Func TestPropertyGetter; + [ReflectedSetter(Name = "TestProperty")] + public static Action TestPropertySetter; + + [ReflectedMethod] + public static Func TestCall; + + + [ReflectedGetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))] + public static Func TestStaticFieldGetter; + [ReflectedSetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))] + public static Action TestStaticFieldSetter; + + [ReflectedGetter(Name = "TestPropertyStatic", Type = typeof(ReflectionTestTarget))] + public static Func TestStaticPropertyGetter; + [ReflectedSetter(Name = "TestPropertyStatic", Type = typeof(ReflectionTestTarget))] + public static Action TestStaticPropertySetter; + + [ReflectedStaticMethod(Type = typeof(ReflectionTestTarget))] + public static Func TestCallStatic; + } + #endregion + + private readonly Random _rand = new Random(); + private int AcquireRandomNum() + { + return _rand.Next(); + } + + #region Instance + [Fact] + public void TestInstanceFieldGet() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + int testNumber = AcquireRandomNum(); + var target = new ReflectionTestTarget + { + TestField = testNumber + }; + Assert.Equal(testNumber, ReflectionTestBinding.TestFieldGetter.Invoke(target)); + } + [Fact] + public void TestInstanceFieldSet() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + int testNumber = AcquireRandomNum(); + var target = new ReflectionTestTarget(); + ReflectionTestBinding.TestFieldSetter.Invoke(target, testNumber); + Assert.Equal(testNumber, target.TestField); + } + + [Fact] + public void TestInstancePropertyGet() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + int testNumber = AcquireRandomNum(); + var target = new ReflectionTestTarget + { + TestProperty = testNumber + }; + Assert.Equal(testNumber, ReflectionTestBinding.TestPropertyGetter.Invoke(target)); + } + + [Fact] + public void TestInstancePropertySet() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + int testNumber = AcquireRandomNum(); + var target = new ReflectionTestTarget(); + ReflectionTestBinding.TestPropertySetter.Invoke(target, testNumber); + Assert.Equal(testNumber, target.TestProperty); + } + + [Fact] + public void TestInstanceInvoke() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + var target = new ReflectionTestTarget(); + Assert.True(ReflectionTestBinding.TestCall.Invoke(target, 1)); + Assert.False(ReflectionTestBinding.TestCall.Invoke(target, -1)); + } + #endregion + + #region Static + [Fact] + public void TestStaticFieldGet() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + int testNumber = AcquireRandomNum(); + ReflectionTestTarget.TestFieldStatic = testNumber; + Assert.Equal(testNumber, ReflectionTestBinding.TestStaticFieldGetter.Invoke()); + } + [Fact] + public void TestStaticFieldSet() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + int testNumber = AcquireRandomNum(); + ReflectionTestBinding.TestStaticFieldSetter.Invoke(testNumber); + Assert.Equal(testNumber, ReflectionTestTarget.TestFieldStatic); + } + + [Fact] + public void TestStaticPropertyGet() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + int testNumber = AcquireRandomNum(); + ReflectionTestTarget.TestPropertyStatic = testNumber; + Assert.Equal(testNumber, ReflectionTestBinding.TestStaticPropertyGetter.Invoke()); + } + + [Fact] + public void TestStaticPropertySet() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + int testNumber = AcquireRandomNum(); + ReflectionTestBinding.TestStaticPropertySetter.Invoke(testNumber); + Assert.Equal(testNumber, ReflectionTestTarget.TestPropertyStatic); + } + + [Fact] + public void TestStaticInvoke() + { + ReflectionManager.Process(typeof(ReflectionTestBinding)); + Assert.True(ReflectionTestBinding.TestCallStatic.Invoke(1)); + Assert.False(ReflectionTestBinding.TestCallStatic.Invoke(-1)); + } + #endregion + #endregion + + #region FieldProvider + public struct FieldRef + { + public FieldInfo Field; + + public FieldRef(FieldInfo f) + { + Field = f; + } + + public override string ToString() + { + return Field.DeclaringType?.FullName + "." + Field.Name; + } + } + + private static bool _init = false; + private static HashSet _getters, _setters, _invokers; + + private static void Init() + { + if (_init) + return; + _getters = new HashSet(); + _setters = new HashSet(); + _invokers = new HashSet(); + + foreach (Type type in typeof(TorchBase).Assembly.GetTypes()) + InternalInit(type); + InternalInit(typeof(ReflectionTestBinding)); + + _init = true; + } + + private static void InternalInit(Type type) + { + foreach (FieldInfo field in type.GetFields(BindingFlags.Static | + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic)) + { + if (field.GetCustomAttribute() != null) + _invokers.Add(new object[] { new FieldRef(field) }); + if (field.GetCustomAttribute() != null) + _getters.Add(new object[] { new FieldRef(field) }); + if (field.GetCustomAttribute() != null) + _setters.Add(new object[] { new FieldRef(field) }); + } + } + + public static IEnumerable Getters + { + get + { + Init(); + return _getters; + } + } + + public static IEnumerable Setters + { + get + { + Init(); + return _setters; + } + } + + public static IEnumerable Invokers + { + get + { + Init(); + return _invokers; + } + } + #endregion + } +} diff --git a/Torch.Tests/Torch.Tests.csproj b/Torch.Tests/Torch.Tests.csproj new file mode 100644 index 0000000..21cbdcb --- /dev/null +++ b/Torch.Tests/Torch.Tests.csproj @@ -0,0 +1,76 @@ + + + + + {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5} + Library + Properties + Torch.Tests + Torch.Tests + v4.6.1 + 512 + + + + 1591,0649 + + + true + $(SolutionDir)\bin-test\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + $(SolutionDir)\bin-test\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + $(SolutionDir)\bin-test\x64\Release\Torch.Tests.xml + + + + + + + + + + + + ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll + + + + + + + + {fba5d932-6254-4a1e-baf4-e229fa94e3c2} + Torch.API + + + {7e01635c-3b67-472e-bcd6-c5539564f214} + Torch + True + + + + + + + \ No newline at end of file diff --git a/Torch.Tests/packages.config b/Torch.Tests/packages.config new file mode 100644 index 0000000..1158086 --- /dev/null +++ b/Torch.Tests/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Torch.sln b/Torch.sln index 6c9a522..f01c4d6 100644 --- a/Torch.sln +++ b/Torch.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26430.14 +VisualStudioVersion = 15.0.26730.8 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch", "Torch\Torch.csproj", "{7E01635C-3B67-472E-BCD6-C5539564F214}" EndProject @@ -16,30 +16,51 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution NLog.config = NLog.config EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|Any CPU.ActiveCfg = Debug|x64 {7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|x64.ActiveCfg = Debug|x64 {7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|x64.Build.0 = Debug|x64 + {7E01635C-3B67-472E-BCD6-C5539564F214}.Release|Any CPU.ActiveCfg = Release|x64 {7E01635C-3B67-472E-BCD6-C5539564F214}.Release|x64.ActiveCfg = Release|x64 {7E01635C-3B67-472E-BCD6-C5539564F214}.Release|x64.Build.0 = Release|x64 + {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|Any CPU.ActiveCfg = Debug|x64 {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|x64.ActiveCfg = Debug|x64 {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|x64.Build.0 = Debug|x64 + {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|Any CPU.ActiveCfg = Release|x64 {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.ActiveCfg = Release|x64 {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.Build.0 = Release|x64 + {E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|Any CPU.ActiveCfg = Debug|x64 {E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.ActiveCfg = Debug|x64 {E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.Build.0 = Debug|x64 + {E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|Any CPU.ActiveCfg = Release|x64 {E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.ActiveCfg = Release|x64 {E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.Build.0 = Release|x64 + {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|Any CPU.ActiveCfg = Debug|x64 {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.ActiveCfg = Debug|x64 {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.Build.0 = Debug|x64 + {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|Any CPU.ActiveCfg = Release|x64 {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.ActiveCfg = Release|x64 {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.Build.0 = Release|x64 + {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|Any CPU.ActiveCfg = Debug|x64 + {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.ActiveCfg = Debug|x64 + {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.Build.0 = Debug|x64 + {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|Any CPU.ActiveCfg = Release|x64 + {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.ActiveCfg = Release|x64 + {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BB51D91F-958D-4B63-A897-3C40642ACD3E} + EndGlobalSection EndGlobal diff --git a/Torch/Managers/MultiplayerManager.cs b/Torch/Managers/MultiplayerManager.cs index a45c25f..a267256 100644 --- a/Torch/Managers/MultiplayerManager.cs +++ b/Torch/Managers/MultiplayerManager.cs @@ -52,7 +52,9 @@ namespace Torch.Managers public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer; private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager)); private static readonly Logger ChatLog = LogManager.GetLogger("Chat"); - private Dictionary _onlinePlayers; + + [ReflectedGetter(Name = "m_players")] + private static Func> _onlinePlayers; [Dependency] private ChatManager _chatManager; @@ -98,21 +100,19 @@ namespace Torch.Managers /// public IMyPlayer GetPlayerByName(string name) { - ValidateOnlinePlayersList(); - return _onlinePlayers.FirstOrDefault(x => x.Value.DisplayName == name).Value; + return _onlinePlayers.Invoke(MySession.Static.Players).FirstOrDefault(x => x.Value.DisplayName == name).Value; } /// public IMyPlayer GetPlayerBySteamId(ulong steamId) { - ValidateOnlinePlayersList(); - _onlinePlayers.TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p); + _onlinePlayers.Invoke(MySession.Static.Players).TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p); return p; } public ulong GetSteamId(long identityId) { - foreach (var kv in _onlinePlayers) + foreach (var kv in _onlinePlayers.Invoke(MySession.Static.Players)) { if (kv.Value.Identity.IdentityId == identityId) return kv.Key.SteamId; @@ -153,20 +153,12 @@ namespace Torch.Managers } } - private void ValidateOnlinePlayersList() - { - if (_onlinePlayers == null) - _onlinePlayers = MySession.Static.Players.GetPrivateField>("m_players"); - } - private void OnSessionLoaded() { Log.Info("Initializing Steam auth"); MyMultiplayer.Static.ClientKicked += OnClientKicked; MyMultiplayer.Static.ClientLeft += OnClientLeft; - ValidateOnlinePlayersList(); - //TODO: Move these with the methods? if (!RemoveHandlers()) { @@ -175,15 +167,15 @@ namespace Torch.Managers } MyGameService.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse; MyGameService.GameServer.UserGroupStatusResponse += UserGroupStatusResponse; - _members = MyMultiplayer.Static.GetPrivateField>("m_members", true); - if (_members == null) - throw new InvalidOperationException("Unable to get m_members from MyMultiplayer.Static. Is this a dedicated server?"); - _waitingForGroup = MyMultiplayer.Static.GetPrivateField>("m_waitingForGroup", true); - if (_waitingForGroup == null) - throw new InvalidOperationException("Unable to get m_waitingForGroup from MyMultiplayer.Static. Is this a dedicated server?"); - _kickedClients = MyMultiplayer.Static.GetPrivateField>("m_kickedClients", true); - if (_kickedClients == null) - throw new InvalidOperationException("Unable to get m_kickedClients from MyMultiplayer.Static. Is this a dedicated server?"); +// _members = MyMultiplayer.Static.GetPrivateField>("m_members", true); +// if (_members == null) +// throw new InvalidOperationException("Unable to get m_members from MyMultiplayer.Static. Is this a dedicated server?"); +// _waitingForGroup = MyMultiplayer.Static.GetPrivateField>("m_waitingForGroup", true); +// if (_waitingForGroup == null) +// throw new InvalidOperationException("Unable to get m_waitingForGroup from MyMultiplayer.Static. Is this a dedicated server?"); +// _kickedClients = MyMultiplayer.Static.GetPrivateField>("m_kickedClients", true); +// if (_kickedClients == null) +// throw new InvalidOperationException("Unable to get m_kickedClients from MyMultiplayer.Static. Is this a dedicated server?"); Log.Info("Steam auth initialized"); } @@ -205,9 +197,12 @@ namespace Torch.Managers //TODO: Split the following into a new file? //These methods override some Keen code to allow us full control over client authentication. //This lets us have a server set to private (admins only) or friends (friends of all listed admins) - private List _members; - private HashSet _waitingForGroup; - private Dictionary _kickedClients; + [ReflectedGetter(Name = "m_members")] + private static Func> _members; + [ReflectedGetter(Name = "m_waitingForGroup")] + private static Func> _waitingForGroup; + [ReflectedGetter(Name = "m_kickedClients")] + private static Func> _kickedClients; //private HashSet _waitingForFriends; private Dictionary _gameOwnerIds = new Dictionary(); //private IMultiplayer _multiplayerImplementation; @@ -269,24 +264,24 @@ namespace Torch.Managers private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner) { Log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}"); - if (IsClientBanned(steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner)) + if (IsClientBanned.Invoke(MyMultiplayer.Static, steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner)) { - this.UserRejected(steamID, JoinResult.BannedByAdmins); - RaiseClientKicked(steamID); + UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins); + RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID); } - else if (IsClientKicked(steamOwner)) + else if (IsClientKicked.Invoke(MyMultiplayer.Static, steamOwner)) { - UserRejected(steamID, JoinResult.KickedRecently); - RaiseClientKicked(steamID); + UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently); + RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID); } if (response != JoinResult.OK) { - UserRejected(steamID, response); + UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response); return; } - if (MyMultiplayer.Static.MemberLimit > 0 && this._members.Count - 1 >= MyMultiplayer.Static.MemberLimit) + if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase) MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit) { - UserRejected(steamID, JoinResult.ServerFull); + UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull); return; } if (MySandboxGame.ConfigDedicated.GroupID == 0uL || @@ -298,25 +293,25 @@ namespace Torch.Managers } if ((MyGameServiceAccountType)Reflection.InvokeStaticMethod(typeof(MyGameService), "GetServerAccountType", MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan) { - this.UserRejected(steamID, JoinResult.GroupIdInvalid); + UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid); return; } if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID)) { - this._waitingForGroup.Add(steamID); + _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID); return; } - this.UserRejected(steamID, JoinResult.SteamServersOffline); + UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline); } private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer) { - if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Remove(userId)) + if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId)) { if (member || officer) UserAccepted(userId); else - UserRejected(userId, JoinResult.NotInGroup); + UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup); } } @@ -330,24 +325,13 @@ namespace Torch.Managers PlayerJoined?.Invoke(vm); } - private void UserRejected(ulong steamId, JoinResult reason) - { - Reflection.InvokePrivateMethod(MyMultiplayer.Static, "UserRejected", steamId, reason); - } - - private bool IsClientBanned(ulong steamId) - { - return (bool)Reflection.InvokePrivateMethod(MyMultiplayer.Static, "IsClientBanned", steamId); - } - - private bool IsClientKicked(ulong steamId) - { - return (bool)Reflection.InvokePrivateMethod(MyMultiplayer.Static, "IsClientKicked", steamId); - } - - private void RaiseClientKicked(ulong steamId) - { - Reflection.InvokePrivateMethod(MyMultiplayer.Static, "RaiseClientKicked", steamId); - } + [ReflectedMethod] + private static Action UserRejected; + [ReflectedMethod] + private static Func IsClientBanned; + [ReflectedMethod] + private static Func IsClientKicked; + [ReflectedMethod] + private static Action RaiseClientKicked; } } diff --git a/Torch/Managers/ReflectionManager.cs b/Torch/Managers/ReflectionManager.cs index 1886fb6..624cebb 100644 --- a/Torch/Managers/ReflectionManager.cs +++ b/Torch/Managers/ReflectionManager.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Data; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -10,10 +12,17 @@ using Torch.API; namespace Torch.Managers { - internal interface IReflectedFieldAttribute + public abstract class ReflectedMemberAttribute : Attribute { - string Name { get; set; } - Type Type { get; set; } + /// + /// Name of the member to access. If null, the tagged field's name. + /// + public string Name { get; set; } = null; + + /// + /// Declaring type of the member to access. If null, inferred from the instance argument type. + /// + public Type Type { get; set; } = null; } /// @@ -36,16 +45,8 @@ namespace Torch.Managers /// /// [AttributeUsage(AttributeTargets.Field)] - public class ReflectedGetterAttribute : Attribute, IReflectedFieldAttribute + public class ReflectedGetterAttribute : ReflectedMemberAttribute { - /// - /// Name of the field to get. If null, the tagged field's name. - /// - public string Name { get; set; } = null; - /// - /// Declaring type of the field to get. If null, inferred from the instance argument type. - /// - public Type Type { get; set; } = null; } /// @@ -68,16 +69,8 @@ namespace Torch.Managers /// /// [AttributeUsage(AttributeTargets.Field)] - public class ReflectedSetterAttribute : Attribute, IReflectedFieldAttribute + public class ReflectedSetterAttribute : ReflectedMemberAttribute { - /// - /// Name of the field to get. If null, the tagged field's name. - /// - public string Name { get; set; } = null; - /// - /// Declaring type of the field to get. If null, inferred from the instance argument type. - /// - public Type Type { get; set; } = null; } /// @@ -98,16 +91,8 @@ namespace Torch.Managers /// /// [AttributeUsage(AttributeTargets.Field)] - public class ReflectedMethodAttribute : Attribute + public class ReflectedMethodAttribute : ReflectedMemberAttribute { - /// - /// Name of the method to invoke. If null, the tagged field's name. - /// - public string Name { get; set; } = null; - /// - /// Declaring type of the method to invoke. If null, inferred from the instance argument type. - /// - public Type Type { get; set; } = null; } /// @@ -176,7 +161,7 @@ namespace Torch.Managers foreach (string ns in _namespaceBlacklist) if (t.FullName.StartsWith(ns)) return; - foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) Process(field); } } @@ -202,18 +187,27 @@ namespace Torch.Managers var attr = field.GetCustomAttribute(); if (attr != null) { + if (!field.IsStatic) + throw new ArgumentException("Field must be static to be reflected"); ProcessReflectedMethod(field, attr); return true; } var attr2 = field.GetCustomAttribute(); if (attr2 != null) { + if (!field.IsStatic) + throw new ArgumentException("Field must be static to be reflected"); ProcessReflectedField(field, attr2); return true; } var attr3 = field.GetCustomAttribute(); if (attr3 != null) { + if (!field.IsStatic) + { + throw new ArgumentException("Field must be static to be reflected"); + } + ProcessReflectedField(field, attr3); return true; } @@ -241,6 +235,14 @@ namespace Torch.Managers (attr is ReflectedStaticMethodAttribute ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, trueParameterTypes, null); + if (methodInstance == null) + { + string methodType = attr is ReflectedStaticMethodAttribute ? "static" : "instance"; + string methodParams = string.Join(", ", + trueParameterTypes.Select(x => x.Name)); + throw new NoNullAllowedException( + $"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)); @@ -267,7 +269,7 @@ namespace Torch.Managers return null; } - private static void ProcessReflectedField(FieldInfo field, IReflectedFieldAttribute attr) + private static void ProcessReflectedField(FieldInfo field, ReflectedMemberAttribute attr) { MethodInfo delegateMethod = field.FieldType.GetMethod("Invoke"); ParameterInfo[] parameters = delegateMethod.GetParameters(); @@ -312,7 +314,7 @@ namespace Torch.Managers GetFieldPropRecursive(trueType, trueName, bindingFlags, (a, b, c) => a.GetProperty(b, c)); if (sourceField == null && sourceProperty == null) throw new ArgumentException( - $"Unable to find field or property for {trueName} in {trueType} or its base types", nameof(field)); + $"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(); diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index 08f8962..5ab2905 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -179,6 +179,7 @@ + diff --git a/Torch.Client/TorchAssemblyResolver.cs b/Torch/TorchAssemblyResolver.cs similarity index 97% rename from Torch.Client/TorchAssemblyResolver.cs rename to Torch/TorchAssemblyResolver.cs index ca06fa3..4091508 100644 --- a/Torch.Client/TorchAssemblyResolver.cs +++ b/Torch/TorchAssemblyResolver.cs @@ -19,7 +19,7 @@ namespace Torch.Client public TorchAssemblyResolver(params string[] paths) { - string location = Assembly.GetEntryAssembly().Location; + string location = Assembly.GetEntryAssembly()?.Location ?? GetType().Assembly.Location; location = location != null ? Path.GetDirectoryName(location) + Path.DirectorySeparatorChar : null; _removablePathPrefix = location ?? ""; _paths = paths;