Initial version of profiler

Moved reflected manager into separate files
Extracted MtObservableEvent
Added a patch to Keencode that lets us call the static cctor of MyEntities in the wrong spot
This commit is contained in:
Westin Miller
2017-10-30 02:50:03 -07:00
parent 11dbf83faf
commit 90c91c3ebc
39 changed files with 1923 additions and 468 deletions

4
Jenkinsfile vendored
View File

@@ -39,8 +39,8 @@ node {
} else { } else {
buildMode = "Debug" buildMode = "Debug"
} }
bat "rmdir /Q /S \"bin\"" bat "IF EXIST \"bin\"rmdir /Q /S \"bin\""
bat "rmdir /Q /S \"bin-test\"" bat "IF EXIST \"bin-test\" rmdir /Q /S \"bin-test\""
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64 /t:Clean" bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64 /t:Clean"
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64" bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64"
} }

View File

@@ -221,6 +221,7 @@
<Compile Include="ViewModels\ILazyLoad.cs" /> <Compile Include="ViewModels\ILazyLoad.cs" />
<Compile Include="ViewModels\PluginManagerViewModel.cs" /> <Compile Include="ViewModels\PluginManagerViewModel.cs" />
<Compile Include="ViewModels\PluginViewModel.cs" /> <Compile Include="ViewModels\PluginViewModel.cs" />
<Compile Include="ViewModels\ProfilerViewModel.cs" />
<Compile Include="ViewModels\SessionSettingsViewModel.cs" /> <Compile Include="ViewModels\SessionSettingsViewModel.cs" />
<Compile Include="ViewModels\Entities\VoxelMapViewModel.cs" /> <Compile Include="ViewModels\Entities\VoxelMapViewModel.cs" />
<Compile Include="ViewModels\SteamUserViewModel.cs" /> <Compile Include="ViewModels\SteamUserViewModel.cs" />
@@ -261,6 +262,9 @@
<Compile Include="Views\PluginsControl.xaml.cs"> <Compile Include="Views\PluginsControl.xaml.cs">
<DependentUpon>PluginsControl.xaml</DependentUpon> <DependentUpon>PluginsControl.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\ProfilerConfigControl.xaml.cs">
<DependentUpon>ProfilerConfigControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\TorchUI.xaml.cs"> <Compile Include="Views\TorchUI.xaml.cs">
<DependentUpon>TorchUI.xaml</DependentUpon> <DependentUpon>TorchUI.xaml</DependentUpon>
</Compile> </Compile>
@@ -349,6 +353,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\ProfilerConfigControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\TorchUI.xaml"> <Page Include="Views\TorchUI.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>

View File

@@ -1,4 +1,8 @@
using VRage.Game.ModAPI; using System.Windows.Controls;
using Torch.API.Managers;
using Torch.Collections;
using Torch.Managers.Profiler;
using VRage.Game.ModAPI;
using VRage.ModAPI; using VRage.ModAPI;
using VRageMath; using VRageMath;
@@ -9,6 +13,12 @@ namespace Torch.Server.ViewModels.Entities
protected EntityTreeViewModel Tree { get; } protected EntityTreeViewModel Tree { get; }
public IMyEntity Entity { get; } public IMyEntity Entity { get; }
public long Id => Entity.EntityId; public long Id => Entity.EntityId;
public ProfilerEntryViewModel Profiler
{
get => ProfilerTreeAlias[0];
set => ProfilerTreeAlias[0] = value;
}
public MtObservableList<ProfilerEntryViewModel> ProfilerTreeAlias { get; } = new MtObservableList<ProfilerEntryViewModel>(1){null};
public virtual string Name public virtual string Name
{ {
@@ -46,6 +56,7 @@ namespace Torch.Server.ViewModels.Entities
{ {
Entity = entity; Entity = entity;
Tree = tree; Tree = tree;
Profiler = TorchBase.Instance.Managers.GetManager<ProfilerManager>()?.EntityData(entity, Profiler);
} }
public EntityViewModel() public EntityViewModel()

View File

@@ -2,7 +2,9 @@
using System.Linq; using System.Linq;
using Sandbox.Game.Entities; using Sandbox.Game.Entities;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using Torch.API.Managers;
using Torch.Collections; using Torch.Collections;
using Torch.Managers.Profiler;
using Torch.Server.ViewModels.Blocks; using Torch.Server.ViewModels.Blocks;
namespace Torch.Server.ViewModels.Entities namespace Torch.Server.ViewModels.Entities

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.API.Managers;
using Torch.Collections;
using Torch.Managers.Profiler;
namespace Torch.Server.ViewModels
{
public class ProfilerViewModel : ViewModel
{
public MtObservableList<ProfilerEntryViewModel> ProfilerTreeAlias { get; } = new MtObservableList<ProfilerEntryViewModel>();
private readonly ProfilerManager _manager;
public ProfilerViewModel()
{
_manager = null;
}
public ProfilerViewModel(ProfilerManager profilerManager)
{
_manager = profilerManager;
ProfilerTreeAlias.Add(_manager.SessionData());
ProfilerTreeAlias.Add(_manager.EntitiesData());
}
/// <inheritdoc cref="ProfilerManager.ProfileGridsUpdate"/>
public bool ProfileGridsUpdate
{
get => _manager?.ProfileGridsUpdate ?? false;
set
{
if (_manager != null)
_manager.ProfileGridsUpdate = value;
OnPropertyChanged();
}
}
/// <inheritdoc cref="ProfilerManager.ProfileBlocksUpdate"/>
public bool ProfileBlocksUpdate
{
get => _manager?.ProfileBlocksUpdate ?? false;
set
{
if (_manager != null)
_manager.ProfileBlocksUpdate = value;
OnPropertyChanged();
}
}
/// <inheritdoc cref="ProfilerManager.ProfileEntityComponentsUpdate"/>
public bool ProfileEntityComponentsUpdate
{
get => _manager?.ProfileEntityComponentsUpdate ?? false;
set
{
if (_manager != null)
_manager.ProfileEntityComponentsUpdate = value;
OnPropertyChanged();
}
}
/// <inheritdoc cref="ProfilerManager.ProfileGridSystemUpdates"/>
public bool ProfileGridSystemUpdates
{
get => _manager?.ProfileGridSystemUpdates ?? false;
set
{
if (_manager != null)
_manager.ProfileGridSystemUpdates = value;
OnPropertyChanged();
}
}
/// <inheritdoc cref="ProfilerManager.ProfileSessionComponentsUpdate"/>
public bool ProfileSessionComponentsUpdate
{
get => _manager?.ProfileSessionComponentsUpdate ?? false;
set => _manager.ProfileSessionComponentsUpdate = value;
}
}
}

View File

@@ -44,7 +44,7 @@ namespace Torch.Server
public void BindServer(ITorchServer server) public void BindServer(ITorchServer server)
{ {
_server = (TorchBase)server; _server = (TorchBase)server;
Dispatcher.Invoke(() => Dispatcher.InvokeAsync(() =>
{ {
ChatItems.Inlines.Clear(); ChatItems.Inlines.Clear();
}); });
@@ -59,7 +59,7 @@ namespace Torch.Server
switch (state) switch (state)
{ {
case TorchSessionState.Loading: case TorchSessionState.Loading:
Dispatcher.Invoke(() => ChatItems.Inlines.Clear()); Dispatcher.InvokeAsync(() => ChatItems.Inlines.Clear());
break; break;
case TorchSessionState.Loaded: case TorchSessionState.Loaded:
{ {
@@ -112,7 +112,7 @@ namespace Torch.Server
ChatScroller.ScrollToBottom(); ChatScroller.ScrollToBottom();
} }
else else
Dispatcher.Invoke(() => InsertMessage(msg)); Dispatcher.InvokeAsync(() => InsertMessage(msg));
} }
private void SendButton_Click(object sender, RoutedEventArgs e) private void SendButton_Click(object sender, RoutedEventArgs e)

View File

@@ -3,7 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Torch.Server.Views.Entities" xmlns:profiler="clr-namespace:Torch.Managers.Profiler;assembly=Torch"
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities" xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.DataContext> <UserControl.DataContext>
@@ -18,5 +18,15 @@
<Label Content="Position" Width="100"/> <Label Content="Position" Width="100"/>
<TextBox Text="{Binding Position}" Margin="3" /> <TextBox Text="{Binding Position}" Margin="3" />
</StackPanel> </StackPanel>
<TreeView Margin="3" ItemsSource="{Binding ProfilerTreeAlias}" Name="ProfilerTreeView">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type profiler:ProfilerEntryViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding OwnerName}" Margin="3" MinWidth="200"/>
<TextBlock Text="{Binding UpdateTimeMs, StringFormat=' {0:F4} ms'}" Margin="3" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -0,0 +1,34 @@
<UserControl x:Class="Torch.Server.Views.ProfilerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Torch.Server.Views"
xmlns:profiler="clr-namespace:Torch.Managers.Profiler;assembly=Torch"
xmlns:viewModels="clr-namespace:Torch.Server.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewModels:ProfilerViewModel/>
</UserControl.DataContext>
<DockPanel LastChildFill="True">
<TreeView Margin="3" ItemsSource="{Binding ProfilerTreeAlias}" Name="ProfilerTreeView" DockPanel.Dock="Left" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type profiler:ProfilerEntryViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding OwnerName}" Margin="3" MinWidth="200"/>
<TextBlock Text="{Binding UpdateTimeMs, StringFormat=' {0:F4} ms'}" Margin="3" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel Orientation="Vertical" DockPanel.Dock="Right" Width="150" MaxWidth="150">
<Label Content="Profiling Options" Margin="3" />
<CheckBox IsChecked="{Binding ProfileSessionComponentsUpdate}" Content="Session Component Updates" Margin="3" />
<CheckBox IsChecked="{Binding ProfileGridsUpdate}" Content="Grid Updates" Margin="3" />
<CheckBox IsChecked="{Binding ProfileGridSystemUpdates}" Content="Grid System Updates" Margin="3" />
<CheckBox IsChecked="{Binding ProfileBlocksUpdate}" Content="Block Updates" Margin="3" />
<CheckBox IsChecked="{Binding ProfileEntityComponentsUpdate}" Content="Entity Component Updates" Margin="3" />
</StackPanel>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Torch.API.Managers;
using Torch.Managers.Profiler;
using Torch.Server.ViewModels;
namespace Torch.Server.Views
{
/// <summary>
/// Interaction logic for ProfilerControl.xaml
/// </summary>
public partial class ProfilerControl : UserControl
{
public ProfilerControl()
{
InitializeComponent();
}
public void BindServer(TorchServer server)
{
DataContext = new ProfilerViewModel(server.Managers.GetManager<ProfilerManager>());
}
}
}

View File

@@ -69,6 +69,9 @@
<TabItem Header="Entity Manager"> <TabItem Header="Entity Manager">
<views:EntitiesControl /> <views:EntitiesControl />
</TabItem> </TabItem>
<TabItem Header="Profiler">
<views:ProfilerControl x:Name="Profiler" />
</TabItem>
<TabItem Header="Plugins"> <TabItem Header="Plugins">
<views:PluginsControl x:Name="Plugins" /> <views:PluginsControl x:Name="Plugins" />
</TabItem> </TabItem>

View File

@@ -48,6 +48,7 @@ namespace Torch.Server
Chat.BindServer(server); Chat.BindServer(server);
PlayerList.BindServer(server); PlayerList.BindServer(server);
Plugins.BindServer(server); Plugins.BindServer(server);
Profiler.BindServer(server);
LoadConfig((TorchConfig)server.Config); LoadConfig((TorchConfig)server.Config);
} }

View File

@@ -3,13 +3,10 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Threading;
using Torch.Utils; using Torch.Utils;
namespace Torch.Collections namespace Torch.Collections
@@ -35,6 +32,11 @@ namespace Torch.Collections
_threadViews = new ThreadLocal<ThreadView>(() => new ThreadView(this)); _threadViews = new ThreadLocal<ThreadView>(() => new ThreadView(this));
} }
/// <summary>
/// Should this observable collection actually dispatch events.
/// </summary>
public bool NotificationsEnabled { get; protected set; } = true;
/// <summary> /// <summary>
/// Takes a snapshot of this collection. Note: This call is only done when a read lock is acquired. /// Takes a snapshot of this collection. Note: This call is only done when a read lock is acquired.
/// </summary> /// </summary>
@@ -92,11 +94,13 @@ namespace Torch.Collections
/// <inheritdoc/> /// <inheritdoc/>
public bool Remove(TV item) public bool Remove(TV item)
{ {
using(Lock.UpgradableReadUsing()) { using (Lock.UpgradableReadUsing())
{
int? oldIndex = (Backing as IList<TV>)?.IndexOf(item); int? oldIndex = (Backing as IList<TV>)?.IndexOf(item);
if (oldIndex == -1) if (oldIndex == -1)
return false; return false;
using(Lock.WriteUsing()) { using (Lock.WriteUsing())
{
if (!Backing.Remove(item)) if (!Backing.Remove(item))
return false; return false;
MarkSnapshotsDirty(); MarkSnapshotsDirty();
@@ -125,6 +129,56 @@ namespace Torch.Collections
#endregion #endregion
#region Event Wrappers #region Event Wrappers
private readonly WeakReference<DeferredUpdateToken> _deferredSnapshot = new WeakReference<DeferredUpdateToken>(null);
private bool _deferredSnapshotTaken = false;
/// <summary>
/// Disposable that stops update signals and signals a full refresh when disposed.
/// </summary>
public IDisposable DeferredUpdate()
{
using (Lock.WriteUsing())
{
if (_deferredSnapshotTaken)
return new DummyToken();
DeferredUpdateToken token;
if (!_deferredSnapshot.TryGetTarget(out token))
_deferredSnapshot.SetTarget(token = new DeferredUpdateToken());
token.SetCollection(this);
return token;
}
}
private struct DummyToken : IDisposable
{
public void Dispose()
{
}
}
private class DeferredUpdateToken : IDisposable
{
private MtObservableCollection<TC, TV> _collection;
internal void SetCollection(MtObservableCollection<TC, TV> c)
{
c._deferredSnapshotTaken = true;
_collection = c;
c.NotificationsEnabled = false;
}
public void Dispose()
{
using (_collection.Lock.WriteUsing())
{
_collection.NotificationsEnabled = true;
_collection.OnPropertyChanged(nameof(Count));
_collection.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
_collection._deferredSnapshotTaken = false;
}
}
}
protected void OnPropertyChanged(string propName) protected void OnPropertyChanged(string propName)
{ {
NotifyEvent(this, new PropertyChangedEventArgs(propName)); NotifyEvent(this, new PropertyChangedEventArgs(propName));
@@ -133,20 +187,23 @@ namespace Torch.Collections
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{ {
NotifyEvent(this, e); NotifyEvent(this, e);
OnPropertyChanged("Item[]");
} }
protected void NotifyEvent(object sender, PropertyChangedEventArgs args) protected void NotifyEvent(object sender, PropertyChangedEventArgs args)
{ {
if (NotificationsEnabled)
_propertyChangedEvent.Raise(sender, args); _propertyChangedEvent.Raise(sender, args);
} }
protected void NotifyEvent(object sender, NotifyCollectionChangedEventArgs args) protected void NotifyEvent(object sender, NotifyCollectionChangedEventArgs args)
{ {
if (NotificationsEnabled)
_collectionChangedEvent.Raise(sender, args); _collectionChangedEvent.Raise(sender, args);
} }
private readonly DispatcherEvent<PropertyChangedEventArgs, PropertyChangedEventHandler> _propertyChangedEvent = private readonly MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler> _propertyChangedEvent =
new DispatcherEvent<PropertyChangedEventArgs, PropertyChangedEventHandler>(); new MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler>();
/// <inheritdoc/> /// <inheritdoc/>
public event PropertyChangedEventHandler PropertyChanged public event PropertyChangedEventHandler PropertyChanged
{ {
@@ -154,85 +211,14 @@ namespace Torch.Collections
remove => _propertyChangedEvent.Remove(value); remove => _propertyChangedEvent.Remove(value);
} }
private readonly DispatcherEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler> _collectionChangedEvent = private readonly MtObservableEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler> _collectionChangedEvent =
new DispatcherEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler>(); new MtObservableEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler>();
/// <inheritdoc/> /// <inheritdoc/>
public event NotifyCollectionChangedEventHandler CollectionChanged public event NotifyCollectionChangedEventHandler CollectionChanged
{ {
add => _collectionChangedEvent.Add(value); add => _collectionChangedEvent.Add(value);
remove => _collectionChangedEvent.Remove(value); remove => _collectionChangedEvent.Remove(value);
} }
/// <summary>
/// Event that invokes handlers registered by dispatchers on dispatchers.
/// </summary>
/// <typeparam name="TEvtArgs">Event argument type</typeparam>
/// <typeparam name="TEvtHandle">Event handler delegate type</typeparam>
private sealed class DispatcherEvent<TEvtArgs, TEvtHandle> where TEvtArgs : EventArgs
{
private delegate void DelInvokeHandler(TEvtHandle handler, object sender, TEvtArgs args);
private static readonly DelInvokeHandler _invokeDirectly;
static DispatcherEvent()
{
MethodInfo invoke = typeof(TEvtHandle).GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
Debug.Assert(invoke != null, "No invoke method on handler type");
_invokeDirectly = (DelInvokeHandler)Delegate.CreateDelegate(typeof(DelInvokeHandler), invoke);
}
private static Dispatcher CurrentDispatcher => Dispatcher.FromThread(Thread.CurrentThread);
private event EventHandler<TEvtArgs> _event;
internal void Raise(object sender, TEvtArgs args)
{
_event?.Invoke(sender, args);
}
internal void Add(TEvtHandle evt)
{
if (evt == null)
return;
_event += new DispatcherDelegate(evt).Invoke;
}
internal void Remove(TEvtHandle evt)
{
if (_event == null || evt == null)
return;
Delegate[] invokeList = _event.GetInvocationList();
for (int i = invokeList.Length - 1; i >= 0; i--)
{
var wrapper = (DispatcherDelegate)invokeList[i].Target;
if (wrapper._delegate.Equals(evt))
{
_event -= wrapper.Invoke;
return;
}
}
}
private struct DispatcherDelegate
{
private readonly Dispatcher _dispatcher;
internal readonly TEvtHandle _delegate;
internal DispatcherDelegate(TEvtHandle del)
{
_dispatcher = CurrentDispatcher;
_delegate = del;
}
public void Invoke(object sender, TEvtArgs args)
{
if (_dispatcher == null || _dispatcher == CurrentDispatcher)
_invokeDirectly(_delegate, sender, args);
else
// (Delegate) (object) == dual cast so that the compiler likes it
_dispatcher.BeginInvoke((Delegate)(object)_delegate, DispatcherPriority.DataBind, sender, args);
}
}
}
#endregion #endregion

View File

@@ -91,12 +91,6 @@ namespace Torch.Collections
/// <inheritdoc cref="Keys"/> /// <inheritdoc cref="Keys"/>
private ProxyCollection<TV> ObservableValues { get; } private ProxyCollection<TV> ObservableValues { get; }
internal void RaiseFullReset()
{
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary> /// <summary>
/// Proxy collection capable of raising notifications when the parent collection changes. /// Proxy collection capable of raising notifications when the parent collection changes.
/// </summary> /// </summary>

View File

@@ -0,0 +1,80 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Windows.Threading;
namespace Torch.Collections
{
/// <summary>
/// Event that invokes handlers registered by dispatchers on dispatchers.
/// </summary>
/// <typeparam name="TEvtArgs">Event argument type</typeparam>
/// <typeparam name="TEvtHandle">Event handler delegate type</typeparam>
internal sealed class MtObservableEvent<TEvtArgs, TEvtHandle> where TEvtArgs : EventArgs
{
private delegate void DelInvokeHandler(TEvtHandle handler, object sender, TEvtArgs args);
private static readonly DelInvokeHandler _invokeDirectly;
static MtObservableEvent()
{
MethodInfo invoke = typeof(TEvtHandle).GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
Debug.Assert(invoke != null, "No invoke method on handler type");
_invokeDirectly = (DelInvokeHandler)Delegate.CreateDelegate(typeof(DelInvokeHandler), invoke);
}
private static Dispatcher CurrentDispatcher => Dispatcher.FromThread(Thread.CurrentThread);
private event EventHandler<TEvtArgs> Event;
internal void Raise(object sender, TEvtArgs args)
{
Event?.Invoke(sender, args);
}
internal void Add(TEvtHandle evt)
{
if (evt == null)
return;
Event += new DispatcherDelegate(evt).Invoke;
}
internal void Remove(TEvtHandle evt)
{
if (Event == null || evt == null)
return;
Delegate[] invokeList = Event.GetInvocationList();
for (int i = invokeList.Length - 1; i >= 0; i--)
{
var wrapper = (DispatcherDelegate)invokeList[i].Target;
if (wrapper._delegate.Equals(evt))
{
Event -= wrapper.Invoke;
return;
}
}
}
private struct DispatcherDelegate
{
private readonly Dispatcher _dispatcher;
internal readonly TEvtHandle _delegate;
internal DispatcherDelegate(TEvtHandle del)
{
_dispatcher = CurrentDispatcher;
_delegate = del;
}
public void Invoke(object sender, TEvtArgs args)
{
if (_dispatcher == null || _dispatcher == CurrentDispatcher)
_invokeDirectly(_delegate, sender, args);
else
// (Delegate) (object) == dual cast so that the compiler likes it
_dispatcher.BeginInvoke((Delegate)(object)_delegate, DispatcherPriority.DataBind, sender, args);
}
}
}
}

View File

@@ -106,7 +106,8 @@ namespace Torch.Collections
/// </summary> /// </summary>
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null) public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
{ {
using (Lock.ReadUsing()) using (DeferredUpdate())
using (Lock.WriteUsing())
{ {
comparer = comparer ?? Comparer<TKey>.Default; comparer = comparer ?? Comparer<TKey>.Default;
if (Backing is List<T> lst) if (Backing is List<T> lst)
@@ -118,8 +119,6 @@ namespace Torch.Collections
foreach (T v in sortedItems) foreach (T v in sortedItems)
Backing.Add(v); Backing.Add(v);
} }
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
} }
} }
} }

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
using Torch.Managers.PatchManager.MSIL;
using Torch.Managers.PatchManager.Transpile;
namespace Torch.Managers.PatchManager
{
/// <summary>
/// Functions that let you read and write MSIL to methods directly.
/// </summary>
public class PatchUtilities
{
/// <summary>
/// Gets the content of a method as an instruction stream
/// </summary>
/// <param name="method">Method to examine</param>
/// <returns>instruction stream</returns>
public static IEnumerable<MsilInstruction> ReadInstructions(MethodBase method)
{
var context = new MethodContext(method);
context.Read();
context.CheckIntegrity();
return context.Instructions;
}
/// <summary>
/// Writes the given instruction stream to the given IL generator, fixing short branch instructions.
/// </summary>
/// <param name="insn">Instruction stream</param>
/// <param name="generator">Output</param>
public static void EmitInstructions(IEnumerable<MsilInstruction> insn, LoggingIlGenerator generator)
{
MethodTranspiler.Emit(insn, generator);
}
}
}

View File

@@ -27,6 +27,8 @@ namespace Torch.Managers.PatchManager.Transpile
{ {
if (parameter.Name.Equals("__methodBody")) if (parameter.Name.Equals("__methodBody"))
paramList.Add(baseMethod.GetMethodBody()); paramList.Add(baseMethod.GetMethodBody());
else if (parameter.Name.Equals("__methodBase"))
paramList.Add(baseMethod);
else if (parameter.Name.Equals("__localCreator")) else if (parameter.Name.Equals("__localCreator"))
paramList.Add(localCreator); paramList.Add(localCreator);
else if (parameter.ParameterType == typeof(IEnumerable<MsilInstruction>)) else if (parameter.ParameterType == typeof(IEnumerable<MsilInstruction>))
@@ -42,6 +44,12 @@ namespace Torch.Managers.PatchManager.Transpile
k.Emit(output); k.Emit(output);
} }
internal static void Emit(IEnumerable<MsilInstruction> input, LoggingIlGenerator output)
{
foreach (MsilInstruction k in FixBranchAndReturn(input, null))
k.Emit(output);
}
private static IEnumerable<MsilInstruction> FixBranchAndReturn(IEnumerable<MsilInstruction> insn, Label? retTarget) private static IEnumerable<MsilInstruction> FixBranchAndReturn(IEnumerable<MsilInstruction> insn, Label? retTarget)
{ {
foreach (MsilInstruction i in insn) foreach (MsilInstruction i in insn)

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Torch.Collections;
namespace Torch.Managers.Profiler
{
public class FatProfilerEntry : SlimProfilerEntry
{
private readonly ConditionalWeakTable<object, SlimProfilerEntry>.CreateValueCallback
ChildUpdateTimeCreateValueFat;
private readonly ConditionalWeakTable<object, SlimProfilerEntry>.CreateValueCallback
ChildUpdateTimeCreateValueSlim;
internal readonly ConditionalWeakTable<object, SlimProfilerEntry> ChildUpdateTime = new ConditionalWeakTable<object, SlimProfilerEntry>();
internal FatProfilerEntry() : this(null)
{
}
internal FatProfilerEntry(FatProfilerEntry fat) : base(fat)
{
ChildUpdateTimeCreateValueFat = (key) =>
{
var result = new FatProfilerEntry(this);
lock (ProfilerData.ProfilingEntriesAll)
ProfilerData.ProfilingEntriesAll.Add(new WeakReference<SlimProfilerEntry>(result));
return result;
};
ChildUpdateTimeCreateValueSlim = (key) =>
{
var result = new SlimProfilerEntry(this);
lock (ProfilerData.ProfilingEntriesAll)
ProfilerData.ProfilingEntriesAll.Add(new WeakReference<SlimProfilerEntry>(result));
return result;
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal SlimProfilerEntry GetSlim(object key)
{
return ChildUpdateTime.GetValue(key, ChildUpdateTimeCreateValueSlim);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal FatProfilerEntry GetFat(object key)
{
return (FatProfilerEntry) ChildUpdateTime.GetValue(key, ChildUpdateTimeCreateValueFat);
}
}
}

View File

@@ -0,0 +1,189 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using NLog;
using Sandbox.Definitions;
using Sandbox.Game.Entities;
using Sandbox.Game.Entities.Cube;
using Torch.Utils;
using VRage.Game.Components;
using VRage.Game.Entity;
using VRage.Game.ModAPI;
using VRage.ModAPI;
namespace Torch.Managers.Profiler
{
/// <summary>
/// Indicates a "fixed" profiler entry. These always exist and will not be moved.
/// </summary>
internal enum ProfilerFixedEntry
{
Entities = 0,
Session = 1,
Count = 2
}
/// <summary>
/// Class that stores all the timing associated with the profiler. Use <see cref="ProfilerManager"/> for observable views into this data.
/// </summary>
internal class ProfilerData
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
internal static bool ProfileGridsUpdate = true;
internal static bool ProfileBlocksUpdate = true;
internal static bool ProfileEntityComponentsUpdate = true;
internal static bool ProfileGridSystemUpdates = true;
internal static bool ProfileSessionComponentsUpdate = true;
private const BindingFlags _instanceFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
private const BindingFlags _staticFlags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
private static MethodInfo Method(Type type, string name, BindingFlags flags)
{
return type.GetMethod(name, flags) ?? throw new Exception($"Couldn't find method {name} on {type}");
}
internal static readonly MethodInfo ProfilerEntryStart = Method(typeof(SlimProfilerEntry), nameof(SlimProfilerEntry.Start), _instanceFlags);
internal static readonly MethodInfo ProfilerEntryStop = Method(typeof(SlimProfilerEntry), nameof(SlimProfilerEntry.Stop), _instanceFlags);
internal static readonly MethodInfo GetEntityProfiler = Method(typeof(ProfilerData), nameof(EntityEntry), _staticFlags);
internal static readonly MethodInfo GetGridSystemProfiler = Method(typeof(ProfilerData), nameof(GridSystemEntry), _staticFlags);
internal static readonly MethodInfo GetEntityComponentProfiler = Method(typeof(ProfilerData), nameof(EntityComponentEntry), _staticFlags);
internal static readonly MethodInfo GetSessionComponentProfiler = Method(typeof(ProfilerData), nameof(SessionComponentEntry), _staticFlags);
internal static readonly MethodInfo DoRotateEntries = Method(typeof(ProfilerData), nameof(RotateEntries), _staticFlags);
internal static ProfilerEntryViewModel BindView(ProfilerEntryViewModel cache = null)
{
if (cache != null)
return cache;
lock (BoundViewModels)
BoundViewModels.Add(new WeakReference<ProfilerEntryViewModel>(cache = new ProfilerEntryViewModel()));
return cache;
}
internal static readonly List<WeakReference<ProfilerEntryViewModel>> BoundViewModels = new List<WeakReference<ProfilerEntryViewModel>>();
#region Rotation
public const int RotateInterval = 300;
private static int _rotateIntervalCounter = 0;
private static void RotateEntries()
{
if (_rotateIntervalCounter++ > RotateInterval)
{
_rotateIntervalCounter = 0;
lock (ProfilingEntriesAll)
{
var i = 0;
while (i < ProfilingEntriesAll.Count)
{
if (ProfilingEntriesAll[i].TryGetTarget(out SlimProfilerEntry result))
{
result.Rotate();
i++;
}
else
{
ProfilingEntriesAll.RemoveAtFast(i);
}
}
}
lock (BoundViewModels)
{
var i = 0;
while (i < BoundViewModels.Count)
{
if (BoundViewModels[i].TryGetTarget(out ProfilerEntryViewModel model) && model.Update())
{
i++;
}
else
{
BoundViewModels.RemoveAtFast(i);
}
}
}
}
}
#endregion
#region Internal Access
internal static readonly List<WeakReference<SlimProfilerEntry>> ProfilingEntriesAll = new List<WeakReference<SlimProfilerEntry>>();
internal static readonly FatProfilerEntry[] Fixed;
internal static FatProfilerEntry FixedProfiler(ProfilerFixedEntry item)
{
return Fixed[(int)item] ?? throw new InvalidOperationException($"Fixed profiler {item} doesn't exist");
}
static ProfilerData()
{
Fixed = new FatProfilerEntry[(int)ProfilerFixedEntry.Count];
lock (ProfilingEntriesAll)
for (var i = 0; i < Fixed.Length; i++)
{
Fixed[i] = new FatProfilerEntry();
ProfilingEntriesAll.Add(new WeakReference<SlimProfilerEntry>(Fixed[i]));
}
}
// ReSharper disable ConvertToConstant.Local
// Don't make these constants. We need to keep the reference alive for the weak table.
private static readonly string _gridUpdateBlocks = "Blocks";
private static readonly string _gridUpdateSystems = "Systems";
private static readonly string _components = "Components";
// ReSharper restore ConvertToConstant.Local
internal static FatProfilerEntry EntityEntry(IMyEntity entity)
{
if (entity == null)
return null;
if (entity is MyCubeBlock block)
{
if (!ProfileBlocksUpdate || !ProfileGridsUpdate)
return null;
return EntityEntry(block.CubeGrid)?.GetFat(_gridUpdateBlocks)
?.GetFat(block.BlockDefinition);
}
if (entity is MyCubeGrid)
{
// ReSharper disable once ConvertIfStatementToReturnStatement
if (!ProfileGridsUpdate)
return null;
return FixedProfiler(ProfilerFixedEntry.Entities)?.GetFat(entity);
}
return null;
}
// Arguments ordered in this BS way for ease of IL use (dup)
internal static SlimProfilerEntry GridSystemEntry(object system, IMyCubeGrid grid)
{
// ReSharper disable once ConvertIfStatementToReturnStatement
if (!ProfileGridSystemUpdates || !ProfileGridsUpdate || system == null)
return null;
Debug.Assert(!system.GetType().IsValueType, "Grid system was a value type. Not good.");
return EntityEntry(grid)?.GetFat(_gridUpdateSystems)?.GetSlim(system);
}
internal static SlimProfilerEntry EntityComponentEntry(MyEntityComponentBase component)
{
if (!ProfileEntityComponentsUpdate || component == null || component is MyCompositeGameLogicComponent)
return null;
return EntityEntry(component.Entity)?.GetFat(_components)?.GetSlim(component);
}
internal static SlimProfilerEntry SessionComponentEntry(MySessionComponentBase component)
{
if (!ProfileSessionComponentsUpdate || component == null)
return null;
return FixedProfiler(ProfilerFixedEntry.Session)?.GetSlim(component);
}
#endregion
}
}

View File

@@ -0,0 +1,211 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using NLog;
using Torch.Collections;
using Torch.Utils;
using VRage.Game;
using VRage.Game.Components;
using VRage.Game.ModAPI;
using VRage.ModAPI;
namespace Torch.Managers.Profiler
{
public class ProfilerEntryViewModel : INotifyPropertyChanged
{
#pragma warning disable 649
[ReflectedGetter(Name = "Keys")]
private static readonly Func<ConditionalWeakTable<object, SlimProfilerEntry>, ICollection<object>> _weakTableKeys;
#pragma warning restore 649
public string OwnerName { get; private set; } = "";
public double UpdateTime { get; private set; }
public double UpdateTimeMs => 1000 * UpdateTime;
public double UpdateLoadPercent => 100 * UpdateTime / MyEngineConstants.UPDATE_STEP_SIZE_IN_SECONDS;
public MtObservableList<ProfilerEntryViewModel> Children { get; } = new MtObservableList<ProfilerEntryViewModel>();
private ProfilerFixedEntry _fixedEntry = ProfilerFixedEntry.Count;
private readonly WeakReference<object> _owner = new WeakReference<object>(null);
private WeakReference<object>[] _getterExtra;
private Func<SlimProfilerEntry> _getter;
public bool IsExpanded { get; set; }
public bool IsSelected { get; set; }
#region Getter Impls
private SlimProfilerEntry GetterImplEntity()
{
return _owner.TryGetTarget(out var own) ? ProfilerData.EntityEntry((IMyEntity)own) : null;
}
private SlimProfilerEntry GetterImplEntityComponent()
{
return _owner.TryGetTarget(out var own)
? ProfilerData.EntityComponentEntry((MyEntityComponentBase)own)
: null;
}
private SlimProfilerEntry GetterImplGridSystem()
{
return _owner.TryGetTarget(out var own) && _getterExtra[0].TryGetTarget(out object grd) ? ProfilerData.GridSystemEntry(own, (IMyCubeGrid)grd) : null;
}
#endregion
#region SetTarget
internal void SetTarget(ProfilerFixedEntry owner)
{
if (owner == ProfilerFixedEntry.Count)
throw new ArgumentOutOfRangeException(nameof(owner), "Must not be the count enum");
_fixedEntry = owner;
_owner.SetTarget(null);
_getterExtra = null;
// we can capture here since its a value type
_getter = () => ProfilerData.FixedProfiler(owner);
}
internal void SetTarget(IMyEntity owner)
{
_fixedEntry = ProfilerFixedEntry.Count;
_owner.SetTarget(owner);
_getterExtra = new WeakReference<object>[0];
_getter = GetterImplEntity;
}
internal void SetTarget(MyEntityComponentBase owner)
{
_fixedEntry = ProfilerFixedEntry.Count;
_owner.SetTarget(owner);
_getterExtra = new WeakReference<object>[0];
_getter = GetterImplEntityComponent;
}
internal void SetTarget(IMyCubeGrid grid, object owner)
{
_fixedEntry = ProfilerFixedEntry.Count;
_owner.SetTarget(owner);
_getterExtra = new[] { new WeakReference<object>(grid) };
_getter = GetterImplGridSystem;
}
#endregion
/// <summary>
/// Called to update the values of this view model without changing the target.
/// </summary>
/// <returns>False if the target was lost</returns>
internal bool Update()
{
object owner;
if (_fixedEntry == ProfilerFixedEntry.Count)
{
bool lostHandle = !_owner.TryGetTarget(out owner);
if (_getterExtra != null && !lostHandle)
foreach (WeakReference<object> ext in _getterExtra)
if (!ext.TryGetTarget(out _))
{
lostHandle = true;
break;
}
if (lostHandle)
{
OwnerName = "Lost Handle";
OnPropertyChanged(nameof(OwnerName));
UpdateTime = 0;
OnPropertyChanged(nameof(UpdateTime));
Children.Clear();
return false;
}
}
else
owner = _fixedEntry;
UpdateInternal(owner, _getter());
return true;
}
private const string _noData = "No Data";
private void UpdateInternal(object owner, SlimProfilerEntry entry)
{
if (entry == null)
{
if (!OwnerName.Equals(_noData))
{
OwnerName = _noData;
OnPropertyChanged(nameof(OwnerName));
}
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (UpdateTime != 0)
{
UpdateTime = 0;
OnPropertyChanged(nameof(UpdateTime));
}
if (Children.Count > 0)
Children.Clear();
return;
}
string ownerId = ProfilerObjectIdentifier.Identify(owner);
if (!ownerId.Equals(OwnerName))
{
OwnerName = ownerId;
OnPropertyChanged(nameof(OwnerName));
}
UpdateTime = entry.UpdateTime;
OnPropertyChanged(nameof(UpdateTime));
if (entry is FatProfilerEntry fat)
{
ICollection<object> keys = _weakTableKeys(fat.ChildUpdateTime);
using (Children.DeferredUpdate())
{
while (Children.Count > keys.Count)
Children.RemoveAt(Children.Count - 1);
var id = 0;
foreach (object key in keys)
{
if (fat.ChildUpdateTime.TryGetValue(key, out SlimProfilerEntry child))
{
if (id >= Children.Count)
{
var result = new ProfilerEntryViewModel();
result.UpdateInternal(key, child);
Children.Add(result);
id++;
}
else
{
Children[id++].UpdateInternal(key, child);
}
}
}
Children.Sort(x => (int) (-x.UpdateTime * 1e6));
}
}
else
{
Children.Clear();
}
}
private readonly MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler> _propertyChangedEvent =
new MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler>();
/// <inheritdoc/>
public event PropertyChangedEventHandler PropertyChanged
{
add => _propertyChangedEvent.Add(value);
remove => _propertyChangedEvent.Remove(value);
}
protected void OnPropertyChanged(string propertyName)
{
if (propertyName == nameof(UpdateTime))
{
OnPropertyChanged(nameof(UpdateLoadPercent));
OnPropertyChanged(nameof(UpdateTimeMs));
}
_propertyChangedEvent.Raise(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Game.Entities;
using Torch.API;
using VRage.Game.Components;
using VRage.ModAPI;
namespace Torch.Managers.Profiler
{
public class ProfilerManager : Manager
{
public ProfilerManager(ITorchBase torchInstance) : base(torchInstance)
{
}
/// <summary>
/// Profile grid related updates.
/// </summary>
public bool ProfileGridsUpdate
{
get => ProfilerData.ProfileGridsUpdate;
set => ProfilerData.ProfileGridsUpdate = value;
}
/// <summary>
/// Profile block updates. Requires <see cref="ProfileGridsUpdate"/>
/// </summary>
public bool ProfileBlocksUpdate
{
get => ProfilerData.ProfileBlocksUpdate;
set => ProfilerData.ProfileBlocksUpdate = value;
}
/// <summary>
/// Profile entity component updates.
/// </summary>
public bool ProfileEntityComponentsUpdate
{
get => ProfilerData.ProfileEntityComponentsUpdate;
set => ProfilerData.ProfileEntityComponentsUpdate = value;
}
/// <summary>
/// Profile grid system updates. Requires <see cref="ProfileGridsUpdate"/>
/// </summary>
public bool ProfileGridSystemUpdates
{
get => ProfilerData.ProfileGridSystemUpdates;
set => ProfilerData.ProfileGridSystemUpdates = value;
}
/// <summary>
/// Profile session component updates.
/// </summary>
public bool ProfileSessionComponentsUpdate
{
get => ProfilerData.ProfileSessionComponentsUpdate;
set => ProfilerData.ProfileSessionComponentsUpdate = value;
}
/// <summary>
/// Gets the profiler information associated with the given entity.
/// </summary>
/// <param name="entity">Entity to get information for</param>
/// <param name="cache">View model to reuse, or null to create a new one</param>
/// <returns>Information</returns>
public ProfilerEntryViewModel EntityData(IMyEntity entity, ProfilerEntryViewModel cache = null)
{
cache = ProfilerData.BindView(cache);
cache.SetTarget(entity);
return cache;
}
/// <summary>
/// Gets the profiler information associated with the given cube grid system.
/// </summary>
/// <param name="grid">Cube grid to query</param>
/// <param name="cubeGridSystem">Cube grid system to query</param>
/// <param name="cache">View model to reuse, or null to create a new one</param>
/// <returns>Information</returns>
public ProfilerEntryViewModel GridSystemData(MyCubeGrid grid, object cubeGridSystem, ProfilerEntryViewModel cache = null)
{
cache = ProfilerData.BindView(cache);
cache.SetTarget(grid, cubeGridSystem);
return cache;
}
/// <summary>
/// Gets the profiler information associated with the given entity component
/// </summary>
/// <param name="component">Component to get information for</param>
/// <param name="cache">View model to reuse, or null to create a new one</param>
/// <returns>Information</returns>
public ProfilerEntryViewModel EntityComponentData(MyEntityComponentBase component, ProfilerEntryViewModel cache = null)
{
cache = ProfilerData.BindView(cache);
cache.SetTarget(component);
return cache;
}
/// <summary>
/// Gets the profiler information associated with all entities
/// </summary>
/// <param name="cache">View model to reuse, or null to create a new one</param>
/// <returns>View model</returns>
public ProfilerEntryViewModel EntitiesData(ProfilerEntryViewModel cache = null)
{
cache = ProfilerData.BindView(cache);
cache.SetTarget(ProfilerFixedEntry.Entities);
return cache;
}
/// <summary>
/// Gets the profiler information associated with the session
/// </summary>
/// <param name="cache">View model to reuse, or null to create a new one</param>
/// <returns>View model</returns>
public ProfilerEntryViewModel SessionData(ProfilerEntryViewModel cache = null)
{
cache = ProfilerData.BindView(cache);
cache.SetTarget(ProfilerFixedEntry.Session);
return cache;
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Entities;
using Sandbox.Game.Entities.Cube;
using Sandbox.Game.Multiplayer;
using Sandbox.ModAPI;
using VRage.Game;
namespace Torch.Managers.Profiler
{
internal static class ProfilerObjectIdentifier
{
/// <summary>
/// Identifies the given object in a human readable name when profiling
/// </summary>
/// <param name="o">object to ID</param>
/// <returns>ID</returns>
public static string Identify(object o)
{
if (o is MyCubeGrid grid)
{
string owners = string.Join(", ", grid.BigOwners.Concat(grid.SmallOwners).Distinct().Select(
x => Sync.Players?.TryGetIdentity(x)?.DisplayName ?? $"Identity[{x}]"));
if (string.IsNullOrWhiteSpace(owners))
owners = "unknown";
return $"{grid.DisplayName ?? ($"{grid.GridSizeEnum} {grid.EntityId}")} owned by [{owners}]";
}
if (o is MyDefinitionBase def)
{
string typeIdSimple = def.Id.TypeId.ToString().Substring("MyObjectBuilder_".Length);
string subtype = def.Id.SubtypeName?.Replace(typeIdSimple, "");
return string.IsNullOrWhiteSpace(subtype) ? typeIdSimple : $"{typeIdSimple}::{subtype}";
}
if (o is string str)
{
return !string.IsNullOrWhiteSpace(str) ? str : "unknown string";
}
if (o is ProfilerFixedEntry fx)
{
string res = fx.ToString();
return !string.IsNullOrWhiteSpace(res) ? res : "unknown fixed";
}
return o?.GetType().Name ?? "unknown";
}
}
}

View File

@@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
using NLog;
using Sandbox.Game.Entities;
using Sandbox.Game.Entities.Cube;
using Sandbox.Game.World;
using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL;
using Torch.Utils;
using Torch.Utils.Reflected;
using VRage.Collections;
using VRage.Game.Components;
using VRage.Game.Entity;
using VRage.Game.Entity.EntityComponents.Interfaces;
using VRage.ModAPI;
namespace Torch.Managers.Profiler
{
[PatchShim, ReflectedLazy]
internal static class ProfilerPatch
{
static ProfilerPatch()
{
ReflectedManager.Process(typeof(ProfilerPatch));
}
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
#region Patch Targets
#pragma warning disable 649
[ReflectedMethodInfo(typeof(MyGameLogic), nameof(MyGameLogic.UpdateBeforeSimulation))]
private static readonly MethodInfo _gameLogicUpdateBeforeSimulation;
[ReflectedMethodInfo(typeof(MyGameLogic), nameof(MyGameLogic.UpdateAfterSimulation))]
private static readonly MethodInfo _gameLogicUpdateAfterSimulation;
[ReflectedMethodInfo(typeof(MyEntities), nameof(MyEntities.UpdateBeforeSimulation))]
private static readonly MethodInfo _entitiesUpdateBeforeSimulation;
[ReflectedMethodInfo(typeof(MyEntities), nameof(MyEntities.UpdateAfterSimulation))]
private static readonly MethodInfo _entitiesUpdateAfterSimulation;
[ReflectedMethodInfo(typeof(Sandbox.Engine.Platform.Game), nameof(Sandbox.Engine.Platform.Game.RunSingleFrame))]
private static readonly MethodInfo _gameRunSingleFrame;
[ReflectedMethodInfo(typeof(MySession), nameof(MySession.UpdateComponents))]
private static readonly MethodInfo _sessionUpdateComponents;
[ReflectedMethodInfo(typeof(MyCubeGridSystems), nameof(MyCubeGridSystems.UpdateBeforeSimulation))]
private static readonly MethodInfo _cubeGridSystemsUpdateBeforeSimulation;
[ReflectedMethodInfo(typeof(MyCubeGridSystems), nameof(MyCubeGridSystems.UpdateBeforeSimulation10))]
private static readonly MethodInfo _cubeGridSystemsUpdateBeforeSimulation10;
[ReflectedMethodInfo(typeof(MyCubeGridSystems), nameof(MyCubeGridSystems.UpdateBeforeSimulation100))]
private static readonly MethodInfo _cubeGridSystemsUpdateBeforeSimulation100;
// [ReflectedMethodInfo(typeof(MyCubeGridSystems), nameof(MyCubeGridSystems.UpdateAfterSimulation))]
// private static readonly MethodInfo _cubeGridSystemsUpdateAfterSimulation;
//
// [ReflectedMethodInfo(typeof(MyCubeGridSystems), nameof(MyCubeGridSystems.UpdateAfterSimulation10))]
// private static readonly MethodInfo _cubeGridSystemsUpdateAfterSimulation10;
[ReflectedMethodInfo(typeof(MyCubeGridSystems), nameof(MyCubeGridSystems.UpdateAfterSimulation100))]
private static readonly MethodInfo _cubeGridSystemsUpdateAfterSimulation100;
[ReflectedFieldInfo(typeof(MyCubeGridSystems), "m_cubeGrid")]
private static readonly FieldInfo _gridSystemsCubeGrid;
#pragma warning restore 649
#endregion
private static MethodInfo _distributedUpdaterIterate;
public static void Patch(PatchContext ctx)
{
_distributedUpdaterIterate = typeof(MyDistributedUpdater<,>).GetMethod("Iterate");
ParameterInfo[] duiP = _distributedUpdaterIterate?.GetParameters();
if (_distributedUpdaterIterate == null || duiP == null || duiP.Length != 1 || typeof(Action<>) != duiP[0].ParameterType.GetGenericTypeDefinition())
{
_log.Error(
$"Unable to find MyDistributedUpdater.Iterate(Delegate) method. Profiling will not function. (Found {_distributedUpdaterIterate}");
return;
}
PatchDistributedUpdate(ctx, _gameLogicUpdateBeforeSimulation);
PatchDistributedUpdate(ctx, _gameLogicUpdateAfterSimulation);
PatchDistributedUpdate(ctx, _entitiesUpdateBeforeSimulation);
PatchDistributedUpdate(ctx, _entitiesUpdateAfterSimulation);
{
MethodInfo patcher = typeof(ProfilerPatch).GetMethod(nameof(TranspilerForUpdate),
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)?
.MakeGenericMethod(typeof(MyCubeGridSystems));
if (patcher == null)
{
_log.Error($"Failed to make generic patching method for cube grid systems");
}
ctx.GetPattern(_cubeGridSystemsUpdateBeforeSimulation).Transpilers.Add(patcher);
ctx.GetPattern(_cubeGridSystemsUpdateBeforeSimulation10).Transpilers.Add(patcher);
ctx.GetPattern(_cubeGridSystemsUpdateBeforeSimulation100).Transpilers.Add(patcher);
// ctx.GetPattern(_cubeGridSystemsUpdateAfterSimulation).Transpilers.Add(patcher);
// ctx.GetPattern(_cubeGridSystemsUpdateAfterSimulation10).Transpilers.Add(patcher);
ctx.GetPattern(_cubeGridSystemsUpdateAfterSimulation100).Transpilers.Add(patcher);
}
{
MethodInfo patcher = typeof(ProfilerPatch).GetMethod(nameof(TranspilerForUpdate),
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)?
.MakeGenericMethod(typeof(MySessionComponentBase));
if (patcher == null)
{
_log.Error($"Failed to make generic patching method for session components");
}
ctx.GetPattern(_sessionUpdateComponents).Transpilers.Add(patcher);
}
ctx.GetPattern(_gameRunSingleFrame).Suffixes.Add(ProfilerData.DoRotateEntries);
}
#region Generalized Update Transpiler
private static bool ShouldProfileMethodCall<T>(MethodBase info)
{
if (info.IsStatic)
return false;
if (typeof(T) != typeof(MyCubeGridSystems) &&
!typeof(T).IsAssignableFrom(info.DeclaringType) &&
(!typeof(MyGameLogicComponent).IsAssignableFrom(typeof(T)) || typeof(IMyGameLogicComponent) != info.DeclaringType))
return false;
if (typeof(T) == typeof(MySessionComponentBase) && info.Name.Equals("Simulate", StringComparison.OrdinalIgnoreCase))
return true;
return info.Name.StartsWith("UpdateBeforeSimulation", StringComparison.OrdinalIgnoreCase) ||
info.Name.StartsWith("UpdateAfterSimulation", StringComparison.OrdinalIgnoreCase);
}
private static IEnumerable<MsilInstruction> TranspilerForUpdate<T>(IEnumerable<MsilInstruction> insn, Func<Type, MsilLocal> __localCreator, MethodBase __methodBase)
{
MethodInfo profilerCall = null;
if (typeof(IMyEntity).IsAssignableFrom(typeof(T)))
profilerCall = ProfilerData.GetEntityProfiler;
else if (typeof(MyEntityComponentBase).IsAssignableFrom(typeof(T)))
profilerCall = ProfilerData.GetEntityComponentProfiler;
else if (typeof(MyCubeGridSystems) == typeof(T))
profilerCall = ProfilerData.GetGridSystemProfiler;
else if (typeof(MySessionComponentBase) == typeof(T))
profilerCall = ProfilerData.GetSessionComponentProfiler;
MsilLocal profilerEntry = profilerCall != null
? __localCreator(typeof(SlimProfilerEntry))
: null;
var usedLocals = new List<MsilLocal>();
var tmpArgument = new Dictionary<Type, Stack<MsilLocal>>();
var foundAny = false;
foreach (MsilInstruction i in insn)
{
if (profilerCall != null && (i.OpCode == OpCodes.Call || i.OpCode == OpCodes.Callvirt) &&
ShouldProfileMethodCall<T>((i.Operand as MsilOperandInline<MethodBase>)?.Value))
{
MethodBase target = ((MsilOperandInline<MethodBase>)i.Operand).Value;
ParameterInfo[] pams = target.GetParameters();
usedLocals.Clear();
foreach (ParameterInfo pam in pams)
{
if (!tmpArgument.TryGetValue(pam.ParameterType, out var stack))
tmpArgument.Add(pam.ParameterType, stack = new Stack<MsilLocal>());
MsilLocal local = stack.Count > 0 ? stack.Pop() : __localCreator(pam.ParameterType);
usedLocals.Add(local);
yield return local.AsValueStore();
}
_log.Debug($"Attaching profiling to {target?.DeclaringType?.FullName}#{target?.Name} in {__methodBase.DeclaringType?.FullName}#{__methodBase.Name} targeting {typeof(T)}");
yield return new MsilInstruction(OpCodes.Dup); // duplicate the object the update is called on
if (typeof(MyCubeGridSystems) == typeof(T))
{
yield return new MsilInstruction(OpCodes.Ldarg_0);
yield return new MsilInstruction(OpCodes.Ldfld).InlineValue(_gridSystemsCubeGrid);
}
yield return new MsilInstruction(OpCodes.Call).InlineValue(profilerCall); // consume object the update is called on
yield return new MsilInstruction(OpCodes.Dup); // Duplicate profiler entry for brnull
yield return profilerEntry.AsValueStore(); // store the profiler entry for later
var skipProfilerOne = new MsilLabel();
yield return new MsilInstruction(OpCodes.Brfalse).InlineTarget(skipProfilerOne); // Brfalse == Brnull
{
yield return profilerEntry.AsValueLoad(); // start the profiler
yield return new MsilInstruction(OpCodes.Call).InlineValue(ProfilerData.ProfilerEntryStart);
}
// consumes from the first Dup
yield return new MsilInstruction(OpCodes.Nop).LabelWith(skipProfilerOne);
for (int j = usedLocals.Count - 1; j >= 0; j--)
yield return usedLocals[j].AsValueLoad();
yield return i;
var skipProfilerTwo = new MsilLabel();
yield return profilerEntry.AsValueLoad();
yield return new MsilInstruction(OpCodes.Brfalse).InlineTarget(skipProfilerTwo); // Brfalse == Brnull
{
yield return profilerEntry.AsValueLoad(); // stop the profiler
yield return new MsilInstruction(OpCodes.Call).InlineValue(ProfilerData.ProfilerEntryStop);
}
yield return new MsilInstruction(OpCodes.Nop).LabelWith(skipProfilerTwo);
foundAny = true;
continue;
}
yield return i;
}
if (!foundAny)
_log.Warn($"Didn't find any update profiling targets for target {typeof(T)} in {__methodBase.DeclaringType?.FullName}#{__methodBase.Name}");
}
#endregion
#region Distributed Update Targeting
private static void PatchDistUpdateDel(PatchContext ctx, MethodBase method)
{
MethodRewritePattern pattern = ctx.GetPattern(method);
MethodInfo patcher = typeof(ProfilerPatch).GetMethod(nameof(TranspilerForUpdate),
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)?
.MakeGenericMethod(method.GetParameters()[0].ParameterType);
if (patcher == null)
{
_log.Error($"Failed to make generic patching method for {method}");
}
pattern.Transpilers.Add(patcher);
}
private static bool IsDistributedIterate(MethodInfo info)
{
if (info == null)
return false;
if (!info.DeclaringType?.IsGenericType ?? true)
return false;
if (info.DeclaringType?.GetGenericTypeDefinition() != _distributedUpdaterIterate.DeclaringType)
return false;
ParameterInfo[] aps = _distributedUpdaterIterate.GetParameters();
ParameterInfo[] ops = info.GetParameters();
if (aps.Length != ops.Length)
return false;
for (var i = 0; i < aps.Length; i++)
if (aps[i].ParameterType.GetGenericTypeDefinition() != ops[i].ParameterType.GetGenericTypeDefinition())
return false;
return true;
}
private static void PatchDistributedUpdate(PatchContext ctx, MethodBase callerMethod)
{
var foundAnyIterate = false;
List<MsilInstruction> msil = PatchUtilities.ReadInstructions(callerMethod).ToList();
for (var i = 0; i < msil.Count; i++)
{
MsilInstruction insn = msil[i];
if ((insn.OpCode == OpCodes.Callvirt || insn.OpCode == OpCodes.Call)
&& IsDistributedIterate((insn.Operand as MsilOperandInline<MethodBase>)?.Value as MethodInfo))
{
foundAnyIterate = true;
// Call to Iterate(). Backtrace up the instruction stack to find the statement creating the delegate.
var foundNewDel = false;
for (int j = i - 1; j >= 1; j--)
{
MsilInstruction insn2 = msil[j];
if (insn2.OpCode == OpCodes.Newobj)
{
Type ctorType = (insn2.Operand as MsilOperandInline<MethodBase>)?.Value?.DeclaringType;
if (ctorType != null && ctorType.IsGenericType &&
ctorType.GetGenericTypeDefinition() == typeof(Action<>))
{
foundNewDel = true;
// Find the instruction loading the function pointer this delegate is created with
MsilInstruction ldftn = msil[j - 1];
if (ldftn.OpCode != OpCodes.Ldftn ||
!(ldftn.Operand is MsilOperandInline<MethodBase> targetMethod))
{
_log.Error(
$"Unable to find ldftn instruction for call to Iterate in {callerMethod.DeclaringType}#{callerMethod}");
}
else
{
_log.Debug($"Patching {targetMethod.Value.DeclaringType}#{targetMethod.Value} for {callerMethod.DeclaringType}#{callerMethod}");
PatchDistUpdateDel(ctx, targetMethod.Value);
}
break;
}
}
}
if (!foundNewDel)
{
_log.Error($"Unable to find new Action() call for Iterate in {callerMethod.DeclaringType}#{callerMethod}");
}
}
}
if (!foundAnyIterate)
_log.Error($"Unable to find any calls to {_distributedUpdaterIterate} in {callerMethod.DeclaringType}#{callerMethod}");
}
#endregion
}
}

View File

@@ -0,0 +1,51 @@
using System.Diagnostics;
using System.Threading;
using NLog;
namespace Torch.Managers.Profiler
{
public class SlimProfilerEntry
{
private readonly FatProfilerEntry _fat;
private readonly Stopwatch _updateWatch = new Stopwatch();
public double UpdateTime { get; private set; } = 0;
private int _watchStarts;
internal SlimProfilerEntry() : this(null)
{
}
internal SlimProfilerEntry(FatProfilerEntry fat)
{
_fat = fat;
}
internal void Start()
{
if (Interlocked.Add(ref _watchStarts, 1) == 1)
{
_fat?.Start();
_updateWatch.Start();
}
}
internal void Stop()
{
if (Interlocked.Add(ref _watchStarts, -1) == 0)
{
_updateWatch.Stop();
_fat?.Stop();
}
}
internal void Rotate()
{
UpdateTime = _updateWatch.Elapsed.TotalSeconds / ProfilerData.RotateInterval;
_updateWatch.Reset();
Debug.Assert(_watchStarts == 0, "A watch wasn't stopped");
_watchStarts = 0;
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Sandbox;
using Sandbox.Game.Entities;
using Torch.Utils;
using VRage.Game.Common;
using VRage.Game.Components;
using VRage.Game.Entity;
using VRage.ObjectBuilders;
namespace Torch.Patches
{
/// <summary>
/// There are places in static ctors where the registered assembly depends on the <see cref="Assembly.GetCallingAssembly"/>.
/// Here we force those registrations with the proper assemblies to ensure they work correctly.
/// </summary>
internal static class RegisterFromCallingAssemblyPatch
{
#pragma warning disable 649
[ReflectedGetter(Name="m_objectFactory", TypeName = "Sandbox.Game.Entities.MyEntityFactory, Sandbox.Game")]
private static readonly Func<MyObjectFactory<MyEntityTypeAttribute, MyEntity>> _entityFactoryObjectFactory;
#pragma warning restore 649
internal static void ForceRegisterAssemblies()
{
// static MyEntities() called by MySandboxGame.ForceStaticCtor
RuntimeHelpers.RunClassConstructor(typeof(MyEntities).TypeHandle);
RegisterFromAssemblySafe(_entityFactoryObjectFactory(), typeof(MySandboxGame).Assembly);
// static MyGuiManager():
// MyGuiControlsFactory.RegisterDescriptorsFromAssembly();
// static MyComponentTypeFactory() called by MyComponentContainer.Add
// _componentFactoryRegisterAssembly(typeof(MyComponentContainer).Assembly);
// static MyObjectPoolManager()
// Render, so should be fine.
}
private static void RegisterDescriptorSafe<TAttribute, TCreatedObjectBase>(
MyObjectFactory<TAttribute, TCreatedObjectBase> factory, TAttribute descriptor, Type type) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
{
if (factory.Attributes.TryGetValue(type, out _))
return;
if (descriptor.ObjectBuilderType != null && factory.TryGetProducedType(descriptor.ObjectBuilderType) != null)
return;
if (typeof(MyObjectBuilder_Base).IsAssignableFrom(descriptor.ProducedType) &&
factory.TryGetProducedType(descriptor.ProducedType) != null)
return;
factory.RegisterDescriptor(descriptor, type);
}
private static void RegisterFromAssemblySafe<TAttribute, TCreatedObjectBase>(MyObjectFactory<TAttribute, TCreatedObjectBase> factory, Assembly assembly) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
{
if (assembly == null)
{
return;
}
foreach (Type type in assembly.GetTypes())
{
foreach (TAttribute descriptor in type.GetCustomAttributes<TAttribute>())
{
RegisterDescriptorSafe(factory, descriptor, type);
}
}
}
}
}

View File

@@ -156,6 +156,7 @@
</Compile> </Compile>
<Compile Include="Collections\MtObservableCollection.cs" /> <Compile Include="Collections\MtObservableCollection.cs" />
<Compile Include="Collections\MtObservableDictionary.cs" /> <Compile Include="Collections\MtObservableDictionary.cs" />
<Compile Include="Collections\MtObservableEvent.cs" />
<Compile Include="Collections\MtObservableList.cs" /> <Compile Include="Collections\MtObservableList.cs" />
<Compile Include="Collections\TransformComparer.cs" /> <Compile Include="Collections\TransformComparer.cs" />
<Compile Include="Collections\TransformEnumerator.cs" /> <Compile Include="Collections\TransformEnumerator.cs" />
@@ -187,11 +188,20 @@
<Compile Include="Managers\PatchManager\PatchContext.cs" /> <Compile Include="Managers\PatchManager\PatchContext.cs" />
<Compile Include="Managers\PatchManager\PatchManager.cs" /> <Compile Include="Managers\PatchManager\PatchManager.cs" />
<Compile Include="Managers\PatchManager\PatchPriorityAttribute.cs" /> <Compile Include="Managers\PatchManager\PatchPriorityAttribute.cs" />
<Compile Include="Managers\PatchManager\PatchUtilities.cs" />
<Compile Include="Managers\PatchManager\Transpile\LoggingILGenerator.cs" /> <Compile Include="Managers\PatchManager\Transpile\LoggingILGenerator.cs" />
<Compile Include="Managers\PatchManager\Transpile\MethodContext.cs" /> <Compile Include="Managers\PatchManager\Transpile\MethodContext.cs" />
<Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" /> <Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" />
<Compile Include="Patches\GameAnalyticsPatch.cs" /> <Compile Include="Patches\GameAnalyticsPatch.cs" />
<Compile Include="Managers\Profiler\FatProfilerEntry.cs" />
<Compile Include="Managers\Profiler\ProfilerData.cs" />
<Compile Include="Managers\Profiler\ProfilerEntryViewModel.cs" />
<Compile Include="Managers\Profiler\ProfilerManager.cs" />
<Compile Include="Managers\Profiler\ProfilerObjectIdentifier.cs" />
<Compile Include="Managers\Profiler\ProfilerPatch.cs" />
<Compile Include="Managers\Profiler\SlimProfilerEntry.cs" />
<Compile Include="Patches\GameStatePatchShim.cs" /> <Compile Include="Patches\GameStatePatchShim.cs" />
<Compile Include="Patches\RegisterFromCallingAssemblyPatch.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SaveGameStatus.cs" /> <Compile Include="SaveGameStatus.cs" />
<Compile Include="Collections\KeyTree.cs" /> <Compile Include="Collections\KeyTree.cs" />
@@ -216,12 +226,23 @@
<Compile Include="Managers\UpdateManager.cs" /> <Compile Include="Managers\UpdateManager.cs" />
<Compile Include="Persistent.cs" /> <Compile Include="Persistent.cs" />
<Compile Include="Plugins\PluginManifest.cs" /> <Compile Include="Plugins\PluginManifest.cs" />
<Compile Include="Utils\Reflected\ReflectedEventReplaceAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedEventReplacer.cs" />
<Compile Include="Utils\Reflected\ReflectedFieldInfoAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedGetterAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedLazyAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedMemberAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedMethodAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedMethodInfoAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedPropertyInfoAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedSetterAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedStaticMethodAttribute.cs" />
<Compile Include="Utils\Reflection.cs" /> <Compile Include="Utils\Reflection.cs" />
<Compile Include="Managers\ScriptingManager.cs" /> <Compile Include="Managers\ScriptingManager.cs" />
<Compile Include="Utils\StringUtils.cs" /> <Compile Include="Utils\StringUtils.cs" />
<Compile Include="Utils\SynchronizationExtensions.cs" /> <Compile Include="Utils\SynchronizationExtensions.cs" />
<Compile Include="Utils\TorchAssemblyResolver.cs" /> <Compile Include="Utils\TorchAssemblyResolver.cs" />
<Compile Include="Utils\ReflectedManager.cs" /> <Compile Include="Utils\Reflected\ReflectedManager.cs" />
<Compile Include="Session\TorchSessionManager.cs" /> <Compile Include="Session\TorchSessionManager.cs" />
<Compile Include="TorchBase.cs" /> <Compile Include="TorchBase.cs" />
<Compile Include="SteamService.cs" /> <Compile Include="SteamService.cs" />

View File

@@ -15,6 +15,7 @@ using Sandbox.Game;
using Sandbox.Game.Multiplayer; using Sandbox.Game.Multiplayer;
using Sandbox.Game.Screens.Helpers; using Sandbox.Game.Screens.Helpers;
using Sandbox.Game.World; using Sandbox.Game.World;
using Sandbox.Graphics.GUI;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using SpaceEngineers.Game; using SpaceEngineers.Game;
using Torch.API; using Torch.API;
@@ -26,12 +27,15 @@ using Torch.Event;
using Torch.Managers; using Torch.Managers;
using Torch.Managers.ChatManager; using Torch.Managers.ChatManager;
using Torch.Managers.PatchManager; using Torch.Managers.PatchManager;
using Torch.Managers.Profiler;
using Torch.Patches; using Torch.Patches;
using Torch.Utils; using Torch.Utils;
using Torch.Session; using Torch.Session;
using VRage.Collections; using VRage.Collections;
using VRage.FileSystem; using VRage.FileSystem;
using VRage.Game; using VRage.Game;
using VRage.Game.Common;
using VRage.Game.Components;
using VRage.Game.ObjectBuilder; using VRage.Game.ObjectBuilder;
using VRage.ObjectBuilders; using VRage.ObjectBuilders;
using VRage.Plugins; using VRage.Plugins;
@@ -134,6 +138,7 @@ namespace Torch
Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new FilesystemManager(this));
Managers.AddManager(new UpdateManager(this)); Managers.AddManager(new UpdateManager(this));
Managers.AddManager(new EventManager(this)); Managers.AddManager(new EventManager(this));
Managers.AddManager(new ProfilerManager(this));
Managers.AddManager(Plugins); Managers.AddManager(Plugins);
TorchAPI.Instance = this; TorchAPI.Instance = this;
} }
@@ -251,6 +256,7 @@ namespace Torch
Debug.Assert(!_init, "Torch instance is already initialized."); Debug.Assert(!_init, "Torch instance is already initialized.");
SpaceEngineersGame.SetupBasicGameInfo(); SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings(); SpaceEngineersGame.SetupPerGameSettings();
RegisterFromCallingAssemblyPatch.ForceRegisterAssemblies();
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null"); Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", ".")); GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));

View File

@@ -0,0 +1,51 @@
using System;
namespace Torch.Utils
{
/// <summary>
/// Attribute used to indicate that the the given field, of type <![CDATA[Func<ReflectedEventReplacer>]]>, should be filled with
/// a function used to create a new event replacer.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedEventReplaceAttribute : Attribute
{
/// <summary>
/// Type that the event is declared in
/// </summary>
public Type EventDeclaringType { get; set; }
/// <summary>
/// Name of the event
/// </summary>
public string EventName { get; set; }
/// <summary>
/// Type that the method to replace is declared in
/// </summary>
public Type TargetDeclaringType { get; set; }
/// <summary>
/// Name of the method to replace
/// </summary>
public string TargetName { get; set; }
/// <summary>
/// Optional parameters of the method to replace. Null to ignore.
/// </summary>
public Type[] TargetParameters { get; set; } = null;
/// <summary>
/// Creates a reflected event replacer attribute to, for the event defined as eventName in eventDeclaringType,
/// replace the method defined as targetName in targetDeclaringType with a custom callback.
/// </summary>
/// <param name="eventDeclaringType">Type the event is declared in</param>
/// <param name="eventName">Name of the event</param>
/// <param name="targetDeclaringType">Type the method to remove is declared in</param>
/// <param name="targetName">Name of the method to remove</param>
public ReflectedEventReplaceAttribute(Type eventDeclaringType, string eventName, Type targetDeclaringType,
string targetName)
{
EventDeclaringType = eventDeclaringType;
EventName = eventName;
TargetDeclaringType = targetDeclaringType;
TargetName = targetName;
}
}
}

View File

@@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Torch.Utils
{
/// <summary>
/// Instance of statefully replacing and restoring the callbacks of an event.
/// </summary>
public class ReflectedEventReplacer
{
private const BindingFlags BindFlagAll = BindingFlags.Static |
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic;
private object _instance;
private Func<IEnumerable<Delegate>> _backingStoreReader;
private Action<Delegate> _callbackAdder;
private Action<Delegate> _callbackRemover;
private readonly ReflectedEventReplaceAttribute _attributes;
private readonly HashSet<Delegate> _registeredCallbacks = new HashSet<Delegate>();
private readonly MethodInfo _targetMethodInfo;
internal ReflectedEventReplacer(ReflectedEventReplaceAttribute attr)
{
_attributes = attr;
FieldInfo backingStore = GetEventBackingField(attr.EventName, attr.EventDeclaringType);
if (backingStore == null)
throw new ArgumentException($"Unable to find backing field for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
EventInfo evtInfo = ReflectedManager.GetFieldPropRecursive(attr.EventDeclaringType, attr.EventName, BindFlagAll, (a, b, c) => a.GetEvent(b, c));
if (evtInfo == null)
throw new ArgumentException($"Unable to find event info for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
_backingStoreReader = () => GetEventsInternal(_instance, backingStore);
_callbackAdder = (x) => evtInfo.AddEventHandler(_instance, x);
_callbackRemover = (x) => evtInfo.RemoveEventHandler(_instance, x);
if (attr.TargetParameters == null)
{
_targetMethodInfo = attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll);
if (_targetMethodInfo == null)
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName} to replace");
}
else
{
_targetMethodInfo =
attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll, null, attr.TargetParameters, null);
if (_targetMethodInfo == null)
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName}){string.Join(", ", attr.TargetParameters.Select(x => x.FullName))}) to replace");
}
}
/// <summary>
/// Test that this replacement can be performed.
/// </summary>
/// <param name="instance">The instance to operate on, or null if static</param>
/// <returns>true if possible, false if unsuccessful</returns>
public bool Test(object instance)
{
_instance = instance;
_registeredCallbacks.Clear();
foreach (Delegate callback in _backingStoreReader.Invoke())
if (callback.Method == _targetMethodInfo)
_registeredCallbacks.Add(callback);
return _registeredCallbacks.Count > 0;
}
private Delegate _newCallback;
/// <summary>
/// Removes the target callback defined in the attribute and replaces it with the provided callback.
/// </summary>
/// <param name="newCallback">The new event callback</param>
/// <param name="instance">The instance to operate on, or null if static</param>
public void Replace(Delegate newCallback, object instance)
{
_instance = instance;
if (_newCallback != null)
throw new Exception("Reflected event replacer is in invalid state: Replace when already replaced");
_newCallback = newCallback;
Test(instance);
if (_registeredCallbacks.Count == 0)
throw new Exception("Reflected event replacer is in invalid state: Nothing to replace");
foreach (Delegate callback in _registeredCallbacks)
_callbackRemover.Invoke(callback);
_callbackAdder.Invoke(_newCallback);
}
/// <summary>
/// Checks if the callback is currently replaced
/// </summary>
public bool Replaced => _newCallback != null;
/// <summary>
/// Removes the callback added by <see cref="Replace"/> and puts the original callback back.
/// </summary>
/// <param name="instance">The instance to operate on, or null if static</param>
public void Restore(object instance)
{
_instance = instance;
if (_newCallback == null)
throw new Exception("Reflected event replacer is in invalid state: Restore when not replaced");
_callbackRemover.Invoke(_newCallback);
foreach (Delegate callback in _registeredCallbacks)
_callbackAdder.Invoke(callback);
_newCallback = null;
}
private static readonly string[] _backingFieldForEvent = { "{0}", "<backing_store>{0}" };
private static FieldInfo GetEventBackingField(string eventName, Type baseType)
{
FieldInfo eventField = null;
Type type = baseType;
while (type != null && eventField == null)
{
for (var i = 0; i < _backingFieldForEvent.Length && eventField == null; i++)
eventField = type.GetField(string.Format(_backingFieldForEvent[i], eventName), BindFlagAll);
type = type.BaseType;
}
return eventField;
}
private static IEnumerable<Delegate> GetEventsInternal(object instance, FieldInfo eventField)
{
if (eventField.GetValue(instance) is MulticastDelegate eventDel)
{
foreach (Delegate handle in eventDel.GetInvocationList())
yield return handle;
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
namespace Torch.Utils
{
/// <summary>
/// Indicates that this field should contain the <see cref="System.Reflection.FieldInfo"/> instance for the given field.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedFieldInfoAttribute : ReflectedMemberAttribute
{
/// <summary>
/// Creates a reflected field info attribute using the given type and name.
/// </summary>
/// <param name="type">Type that contains the member</param>
/// <param name="name">Name of the member</param>
public ReflectedFieldInfoAttribute(Type type, string name)
{
Type = type;
Name = name;
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace Torch.Utils
{
/// <summary>
/// Indicates that this field should contain a delegate capable of retrieving the value of a field.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// [ReflectedGetterAttribute(Name="_instanceField")]
/// private static Func<Example, int> _instanceGetter;
///
/// [ReflectedGetterAttribute(Name="_staticField", Type=typeof(Example))]
/// private static Func<int> _staticGetter;
///
/// private class Example {
/// private int _instanceField;
/// private static int _staticField;
/// }
/// ]]>
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedGetterAttribute : ReflectedMemberAttribute
{
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Utils.Reflected
{
/// <summary>
/// Indicates that the type will perform its own call to <see cref="ReflectedManager.Process(Type)"/>
/// </summary>
public class ReflectedLazyAttribute : Attribute
{
}
}

View File

@@ -10,389 +10,24 @@ using System.Threading.Tasks;
using NLog; using NLog;
using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Multiplayer;
using Torch.API; using Torch.API;
using Torch.Utils.Reflected;
namespace Torch.Utils namespace Torch.Utils
{ {
public abstract class ReflectedMemberAttribute : Attribute
{
/// <summary>
/// Name of the member to access. If null, the tagged field's name.
/// </summary>
public string Name { get; set; } = null;
/// <summary>
/// Declaring type of the member to access. If null, inferred from the instance argument type.
/// </summary>
public Type Type { get; set; } = null;
/// <summary>
/// Assembly qualified name of <see cref="Type"/>
/// </summary>
public string TypeName
{
get => Type?.AssemblyQualifiedName;
set => Type = value == null ? null : Type.GetType(value, true);
}
}
#region MemberInfoAttributes #region MemberInfoAttributes
/// <summary>
/// Indicates that this field should contain the <see cref="System.Reflection.FieldInfo"/> instance for the given field.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedFieldInfoAttribute : ReflectedMemberAttribute
{
/// <summary>
/// Creates a reflected field info attribute using the given type and name.
/// </summary>
/// <param name="type">Type that contains the member</param>
/// <param name="name">Name of the member</param>
public ReflectedFieldInfoAttribute(Type type, string name)
{
Type = type;
Name = name;
}
}
/// <summary>
/// Indicates that this field should contain the <see cref="System.Reflection.MethodInfo"/> instance for the given method.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedMethodInfoAttribute : ReflectedMemberAttribute
{
/// <summary>
/// Creates a reflected method info attribute using the given type and name.
/// </summary>
/// <param name="type">Type that contains the member</param>
/// <param name="name">Name of the member</param>
public ReflectedMethodInfoAttribute(Type type, string name)
{
Type = type;
Name = name;
}
/// <summary>
/// Expected parameters of this method, or null if any parameters are accepted.
/// </summary>
public Type[] Parameters { get; set; } = null;
/// <summary>
/// Assembly qualified names of <see cref="Parameters"/>
/// </summary>
public string[] ParameterNames
{
get => Parameters.Select(x => x.AssemblyQualifiedName).ToArray();
set => Parameters = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
}
/// <summary>
/// Expected return type of this method, or null if any return type is accepted.
/// </summary>
public Type ReturnType { get; set; } = null;
}
/// <summary>
/// Indicates that this field should contain the <see cref="System.Reflection.PropertyInfo"/> instance for the given property.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedPropertyInfoAttribute : ReflectedMemberAttribute
{
/// <summary>
/// Creates a reflected property info attribute using the given type and name.
/// </summary>
/// <param name="type">Type that contains the member</param>
/// <param name="name">Name of the member</param>
public ReflectedPropertyInfoAttribute(Type type, string name)
{
Type = type;
Name = name;
}
}
#endregion #endregion
#region FieldPropGetSet #region FieldPropGetSet
/// <summary>
/// Indicates that this field should contain a delegate capable of retrieving the value of a field.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// [ReflectedGetterAttribute(Name="_instanceField")]
/// private static Func<Example, int> _instanceGetter;
///
/// [ReflectedGetterAttribute(Name="_staticField", Type=typeof(Example))]
/// private static Func<int> _staticGetter;
///
/// private class Example {
/// private int _instanceField;
/// private static int _staticField;
/// }
/// ]]>
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedGetterAttribute : ReflectedMemberAttribute
{
}
/// <summary>
/// Indicates that this field should contain a delegate capable of setting the value of a field.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// [ReflectedSetterAttribute(Name="_instanceField")]
/// private static Action<Example, int> _instanceSetter;
///
/// [ReflectedSetterAttribute(Name="_staticField", Type=typeof(Example))]
/// private static Action<int> _staticSetter;
///
/// private class Example {
/// private int _instanceField;
/// private static int _staticField;
/// }
/// ]]>
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedSetterAttribute : ReflectedMemberAttribute
{
}
#endregion #endregion
#region Invoker #region Invoker
/// <summary>
/// Indicates that this field should contain a delegate capable of invoking an instance method.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// [ReflectedMethodAttribute]
/// private static Func<Example, int, float, string> ExampleInstance;
///
/// private class Example {
/// private int ExampleInstance(int a, float b) {
/// return a + ", " + b;
/// }
/// }
/// ]]>
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedMethodAttribute : ReflectedMemberAttribute
{
/// <summary>
/// When set the parameters types for the method are assumed to be this.
/// </summary>
public Type[] OverrideTypes { get; set; }
/// <summary>
/// Assembly qualified names of <see cref="OverrideTypes"/>
/// </summary>
public string[] OverrideTypeNames
{
get => OverrideTypes.Select(x => x.AssemblyQualifiedName).ToArray();
set => OverrideTypes = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
}
}
/// <summary>
/// Indicates that this field should contain a delegate capable of invoking a static method.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// [ReflectedMethodAttribute(Type = typeof(Example)]
/// private static Func<int, float, string> ExampleStatic;
///
/// private class Example {
/// private static int ExampleStatic(int a, float b) {
/// return a + ", " + b;
/// }
/// }
/// ]]>
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedStaticMethodAttribute : ReflectedMethodAttribute
{
}
#endregion #endregion
#region EventReplacer #region EventReplacer
/// <summary>
/// Instance of statefully replacing and restoring the callbacks of an event.
/// </summary>
public class ReflectedEventReplacer
{
private const BindingFlags BindFlagAll = BindingFlags.Static |
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic;
private object _instance;
private Func<IEnumerable<Delegate>> _backingStoreReader;
private Action<Delegate> _callbackAdder;
private Action<Delegate> _callbackRemover;
private readonly ReflectedEventReplaceAttribute _attributes;
private readonly HashSet<Delegate> _registeredCallbacks = new HashSet<Delegate>();
private readonly MethodInfo _targetMethodInfo;
internal ReflectedEventReplacer(ReflectedEventReplaceAttribute attr)
{
_attributes = attr;
FieldInfo backingStore = GetEventBackingField(attr.EventName, attr.EventDeclaringType);
if (backingStore == null)
throw new ArgumentException($"Unable to find backing field for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
EventInfo evtInfo = ReflectedManager.GetFieldPropRecursive(attr.EventDeclaringType, attr.EventName, BindFlagAll, (a, b, c) => a.GetEvent(b, c));
if (evtInfo == null)
throw new ArgumentException($"Unable to find event info for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
_backingStoreReader = () => GetEventsInternal(_instance, backingStore);
_callbackAdder = (x) => evtInfo.AddEventHandler(_instance, x);
_callbackRemover = (x) => evtInfo.RemoveEventHandler(_instance, x);
if (attr.TargetParameters == null)
{
_targetMethodInfo = attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll);
if (_targetMethodInfo == null)
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName} to replace");
}
else
{
_targetMethodInfo =
attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll, null, attr.TargetParameters, null);
if (_targetMethodInfo == null)
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName}){string.Join(", ", attr.TargetParameters.Select(x => x.FullName))}) to replace");
}
}
/// <summary>
/// Test that this replacement can be performed.
/// </summary>
/// <param name="instance">The instance to operate on, or null if static</param>
/// <returns>true if possible, false if unsuccessful</returns>
public bool Test(object instance)
{
_instance = instance;
_registeredCallbacks.Clear();
foreach (Delegate callback in _backingStoreReader.Invoke())
if (callback.Method == _targetMethodInfo)
_registeredCallbacks.Add(callback);
return _registeredCallbacks.Count > 0;
}
private Delegate _newCallback;
/// <summary>
/// Removes the target callback defined in the attribute and replaces it with the provided callback.
/// </summary>
/// <param name="newCallback">The new event callback</param>
/// <param name="instance">The instance to operate on, or null if static</param>
public void Replace(Delegate newCallback, object instance)
{
_instance = instance;
if (_newCallback != null)
throw new Exception("Reflected event replacer is in invalid state: Replace when already replaced");
_newCallback = newCallback;
Test(instance);
if (_registeredCallbacks.Count == 0)
throw new Exception("Reflected event replacer is in invalid state: Nothing to replace");
foreach (Delegate callback in _registeredCallbacks)
_callbackRemover.Invoke(callback);
_callbackAdder.Invoke(_newCallback);
}
/// <summary>
/// Checks if the callback is currently replaced
/// </summary>
public bool Replaced => _newCallback != null;
/// <summary>
/// Removes the callback added by <see cref="Replace"/> and puts the original callback back.
/// </summary>
/// <param name="instance">The instance to operate on, or null if static</param>
public void Restore(object instance)
{
_instance = instance;
if (_newCallback == null)
throw new Exception("Reflected event replacer is in invalid state: Restore when not replaced");
_callbackRemover.Invoke(_newCallback);
foreach (Delegate callback in _registeredCallbacks)
_callbackAdder.Invoke(callback);
_newCallback = null;
}
private static readonly string[] _backingFieldForEvent = { "{0}", "<backing_store>{0}" };
private static FieldInfo GetEventBackingField(string eventName, Type baseType)
{
FieldInfo eventField = null;
Type type = baseType;
while (type != null && eventField == null)
{
for (var i = 0; i < _backingFieldForEvent.Length && eventField == null; i++)
eventField = type.GetField(string.Format(_backingFieldForEvent[i], eventName), BindFlagAll);
type = type.BaseType;
}
return eventField;
}
private static IEnumerable<Delegate> GetEventsInternal(object instance, FieldInfo eventField)
{
if (eventField.GetValue(instance) is MulticastDelegate eventDel)
{
foreach (Delegate handle in eventDel.GetInvocationList())
yield return handle;
}
}
}
/// <summary>
/// Attribute used to indicate that the the given field, of type <![CDATA[Func<ReflectedEventReplacer>]]>, should be filled with
/// a function used to create a new event replacer.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedEventReplaceAttribute : Attribute
{
/// <summary>
/// Type that the event is declared in
/// </summary>
public Type EventDeclaringType { get; set; }
/// <summary>
/// Name of the event
/// </summary>
public string EventName { get; set; }
/// <summary>
/// Type that the method to replace is declared in
/// </summary>
public Type TargetDeclaringType { get; set; }
/// <summary>
/// Name of the method to replace
/// </summary>
public string TargetName { get; set; }
/// <summary>
/// Optional parameters of the method to replace. Null to ignore.
/// </summary>
public Type[] TargetParameters { get; set; } = null;
/// <summary>
/// Creates a reflected event replacer attribute to, for the event defined as eventName in eventDeclaringType,
/// replace the method defined as targetName in targetDeclaringType with a custom callback.
/// </summary>
/// <param name="eventDeclaringType">Type the event is declared in</param>
/// <param name="eventName">Name of the event</param>
/// <param name="targetDeclaringType">Type the method to remove is declared in</param>
/// <param name="targetName">Name of the method to remove</param>
public ReflectedEventReplaceAttribute(Type eventDeclaringType, string eventName, Type targetDeclaringType,
string targetName)
{
EventDeclaringType = eventDeclaringType;
EventName = eventName;
TargetDeclaringType = targetDeclaringType;
TargetName = targetName;
}
}
#endregion #endregion
/// <summary> /// <summary>
@@ -438,6 +73,7 @@ namespace Torch.Utils
public static void Process(Assembly asm) public static void Process(Assembly asm)
{ {
foreach (Type type in asm.GetTypes()) foreach (Type type in asm.GetTypes())
if (!type.HasAttribute<ReflectedLazyAttribute>())
Process(type); Process(type);
} }

View File

@@ -0,0 +1,26 @@
using System;
namespace Torch.Utils
{
public abstract class ReflectedMemberAttribute : Attribute
{
/// <summary>
/// Name of the member to access. If null, the tagged field's name.
/// </summary>
public string Name { get; set; } = null;
/// <summary>
/// Declaring type of the member to access. If null, inferred from the instance argument type.
/// </summary>
public Type Type { get; set; } = null;
/// <summary>
/// Assembly qualified name of <see cref="Type"/>
/// </summary>
public string TypeName
{
get => Type?.AssemblyQualifiedName;
set => Type = value == null ? null : Type.GetType(value, true);
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Linq;
namespace Torch.Utils
{
/// <summary>
/// Indicates that this field should contain a delegate capable of invoking an instance method.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// [ReflectedMethodAttribute]
/// private static Func<Example, int, float, string> ExampleInstance;
///
/// private class Example {
/// private int ExampleInstance(int a, float b) {
/// return a + ", " + b;
/// }
/// }
/// ]]>
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedMethodAttribute : ReflectedMemberAttribute
{
/// <summary>
/// When set the parameters types for the method are assumed to be this.
/// </summary>
public Type[] OverrideTypes { get; set; }
/// <summary>
/// Assembly qualified names of <see cref="OverrideTypes"/>
/// </summary>
public string[] OverrideTypeNames
{
get => OverrideTypes.Select(x => x.AssemblyQualifiedName).ToArray();
set => OverrideTypes = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Linq;
namespace Torch.Utils
{
/// <summary>
/// Indicates that this field should contain the <see cref="System.Reflection.MethodInfo"/> instance for the given method.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedMethodInfoAttribute : ReflectedMemberAttribute
{
/// <summary>
/// Creates a reflected method info attribute using the given type and name.
/// </summary>
/// <param name="type">Type that contains the member</param>
/// <param name="name">Name of the member</param>
public ReflectedMethodInfoAttribute(Type type, string name)
{
Type = type;
Name = name;
}
/// <summary>
/// Expected parameters of this method, or null if any parameters are accepted.
/// </summary>
public Type[] Parameters { get; set; } = null;
/// <summary>
/// Assembly qualified names of <see cref="Parameters"/>
/// </summary>
public string[] ParameterNames
{
get => Parameters.Select(x => x.AssemblyQualifiedName).ToArray();
set => Parameters = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
}
/// <summary>
/// Expected return type of this method, or null if any return type is accepted.
/// </summary>
public Type ReturnType { get; set; } = null;
}
}

View File

@@ -0,0 +1,22 @@
using System;
namespace Torch.Utils
{
/// <summary>
/// Indicates that this field should contain the <see cref="System.Reflection.PropertyInfo"/> instance for the given property.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedPropertyInfoAttribute : ReflectedMemberAttribute
{
/// <summary>
/// Creates a reflected property info attribute using the given type and name.
/// </summary>
/// <param name="type">Type that contains the member</param>
/// <param name="name">Name of the member</param>
public ReflectedPropertyInfoAttribute(Type type, string name)
{
Type = type;
Name = name;
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace Torch.Utils
{
/// <summary>
/// Indicates that this field should contain a delegate capable of setting the value of a field.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// [ReflectedSetterAttribute(Name="_instanceField")]
/// private static Action<Example, int> _instanceSetter;
///
/// [ReflectedSetterAttribute(Name="_staticField", Type=typeof(Example))]
/// private static Action<int> _staticSetter;
///
/// private class Example {
/// private int _instanceField;
/// private static int _staticField;
/// }
/// ]]>
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedSetterAttribute : ReflectedMemberAttribute
{
}
}

View File

@@ -0,0 +1,26 @@
using System;
namespace Torch.Utils
{
/// <summary>
/// Indicates that this field should contain a delegate capable of invoking a static method.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// [ReflectedMethodAttribute(Type = typeof(Example)]
/// private static Func<int, float, string> ExampleStatic;
///
/// private class Example {
/// private static int ExampleStatic(int a, float b) {
/// return a + ", " + b;
/// }
/// }
/// ]]>
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field)]
public class ReflectedStaticMethodAttribute : ReflectedMethodAttribute
{
}
}