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:
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@@ -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"
|
||||||
}
|
}
|
||||||
|
@@ -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>
|
||||||
|
@@ -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()
|
||||||
|
@@ -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
|
||||||
|
85
Torch.Server/ViewModels/ProfilerViewModel.cs
Normal file
85
Torch.Server/ViewModels/ProfilerViewModel.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
||||||
|
@@ -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>
|
34
Torch.Server/Views/ProfilerConfigControl.xaml
Normal file
34
Torch.Server/Views/ProfilerConfigControl.xaml
Normal 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>
|
36
Torch.Server/Views/ProfilerConfigControl.xaml.cs
Normal file
36
Torch.Server/Views/ProfilerConfigControl.xaml.cs
Normal 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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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>
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
@@ -54,7 +56,7 @@ namespace Torch.Collections
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Add(TV item)
|
public void Add(TV item)
|
||||||
{
|
{
|
||||||
using(Lock.WriteUsing())
|
using (Lock.WriteUsing())
|
||||||
{
|
{
|
||||||
Backing.Add(item);
|
Backing.Add(item);
|
||||||
MarkSnapshotsDirty();
|
MarkSnapshotsDirty();
|
||||||
@@ -66,7 +68,7 @@ namespace Torch.Collections
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
using(Lock.WriteUsing())
|
using (Lock.WriteUsing())
|
||||||
{
|
{
|
||||||
Backing.Clear();
|
Backing.Clear();
|
||||||
MarkSnapshotsDirty();
|
MarkSnapshotsDirty();
|
||||||
@@ -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
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
|
80
Torch/Collections/MtObservableEvent.cs
Normal file
80
Torch/Collections/MtObservableEvent.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
Torch/Managers/PatchManager/PatchUtilities.cs
Normal file
41
Torch/Managers/PatchManager/PatchUtilities.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
||||||
|
50
Torch/Managers/Profiler/FatProfilerEntry.cs
Normal file
50
Torch/Managers/Profiler/FatProfilerEntry.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
189
Torch/Managers/Profiler/ProfilerData.cs
Normal file
189
Torch/Managers/Profiler/ProfilerData.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
211
Torch/Managers/Profiler/ProfilerEntryViewModel.cs
Normal file
211
Torch/Managers/Profiler/ProfilerEntryViewModel.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
Torch/Managers/Profiler/ProfilerManager.cs
Normal file
130
Torch/Managers/Profiler/ProfilerManager.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
Torch/Managers/Profiler/ProfilerObjectIdentifier.cs
Normal file
50
Torch/Managers/Profiler/ProfilerObjectIdentifier.cs
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
305
Torch/Managers/Profiler/ProfilerPatch.cs
Normal file
305
Torch/Managers/Profiler/ProfilerPatch.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
51
Torch/Managers/Profiler/SlimProfilerEntry.cs
Normal file
51
Torch/Managers/Profiler/SlimProfilerEntry.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
Torch/Patches/RegisterFromCallingAssemblyPatch.cs
Normal file
74
Torch/Patches/RegisterFromCallingAssemblyPatch.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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" />
|
||||||
|
@@ -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("_", "."));
|
||||||
|
51
Torch/Utils/Reflected/ReflectedEventReplaceAttribute.cs
Normal file
51
Torch/Utils/Reflected/ReflectedEventReplaceAttribute.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
Torch/Utils/Reflected/ReflectedEventReplacer.cs
Normal file
135
Torch/Utils/Reflected/ReflectedEventReplacer.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Torch/Utils/Reflected/ReflectedFieldInfoAttribute.cs
Normal file
22
Torch/Utils/Reflected/ReflectedFieldInfoAttribute.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Torch/Utils/Reflected/ReflectedGetterAttribute.cs
Normal file
28
Torch/Utils/Reflected/ReflectedGetterAttribute.cs
Normal 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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
15
Torch/Utils/Reflected/ReflectedLazyAttribute.cs
Normal file
15
Torch/Utils/Reflected/ReflectedLazyAttribute.cs
Normal 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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
26
Torch/Utils/Reflected/ReflectedMemberAttribute.cs
Normal file
26
Torch/Utils/Reflected/ReflectedMemberAttribute.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
Torch/Utils/Reflected/ReflectedMethodAttribute.cs
Normal file
40
Torch/Utils/Reflected/ReflectedMethodAttribute.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
Torch/Utils/Reflected/ReflectedMethodInfoAttribute.cs
Normal file
41
Torch/Utils/Reflected/ReflectedMethodInfoAttribute.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
22
Torch/Utils/Reflected/ReflectedPropertyInfoAttribute.cs
Normal file
22
Torch/Utils/Reflected/ReflectedPropertyInfoAttribute.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Torch/Utils/Reflected/ReflectedSetterAttribute.cs
Normal file
28
Torch/Utils/Reflected/ReflectedSetterAttribute.cs
Normal 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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
26
Torch/Utils/Reflected/ReflectedStaticMethodAttribute.cs
Normal file
26
Torch/Utils/Reflected/ReflectedStaticMethodAttribute.cs
Normal 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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user