Pluggable system for providing WPF controls inside the Entity Manager

This commit is contained in:
Westin Miller
2017-10-31 04:18:27 -07:00
parent 90c91c3ebc
commit cd77fe74d5
35 changed files with 593 additions and 1255 deletions

View File

@@ -18,7 +18,7 @@
<logger name="Keen" minlevel="Debug" writeTo="keen" final="true" /> <logger name="Keen" minlevel="Debug" writeTo="keen" final="true" />
<logger name="Keen" writeTo="null" final="true" /> <logger name="Keen" writeTo="null" final="true" />
<logger name="*" minlevel="Info" writeTo="main, console" /> <logger name="*" minlevel="Debug" writeTo="main, console" />
<logger name="Chat" minlevel="Info" writeTo="chat" /> <logger name="Chat" minlevel="Info" writeTo="chat" />
<!--<logger name="Torch.Managers.PatchManager.*" minlevel="Trace" writeTo="patch"/>--> <!--<logger name="Torch.Managers.PatchManager.*" minlevel="Trace" writeTo="patch"/>-->
</rules> </rules>

View File

@@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using NLog;
using NLog.Fluent;
using Torch.API;
using Torch.Collections;
using Torch.Managers;
using Torch.Server.ViewModels.Entities;
using Torch.Utils;
using WeakEntityControlFactoryResult = System.Collections.Generic.KeyValuePair<System.WeakReference<Torch.Server.ViewModels.Entities.EntityViewModel>, System.WeakReference<Torch.Server.ViewModels.Entities.EntityControlViewModel>>;
namespace Torch.Server.Managers
{
/// <summary>
/// Manager that lets users bind random view models to entities in Torch's Entity Manager
/// </summary>
public class EntityControlManager : Manager
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
/// <summary>
/// Creates an entity control manager for the given instance of torch
/// </summary>
/// <param name="torchInstance">Torch instance</param>
internal EntityControlManager(ITorchBase torchInstance) : base(torchInstance)
{
}
private readonly Dictionary<Delegate, List<WeakEntityControlFactoryResult>> _modelFactories = new Dictionary<Delegate, List<WeakEntityControlFactoryResult>>();
private readonly List<Delegate> _controlFactories = new List<Delegate>();
private readonly List<WeakReference<EntityViewModel>> _boundEntityViewModels = new List<WeakReference<EntityViewModel>>();
private readonly ConditionalWeakTable<EntityViewModel, MtObservableList<EntityControlViewModel>> _boundViewModels = new ConditionalWeakTable<EntityViewModel, MtObservableList<EntityControlViewModel>>();
/// <summary>
/// This factory will be used to create component models for matching entity models.
/// </summary>
/// <typeparam name="TEntityBaseModel">entity model type to match</typeparam>
/// <param name="modelFactory">Method to create component model from entity model.</param>
public void RegisterModelFactory<TEntityBaseModel>(Func<TEntityBaseModel, EntityControlViewModel> modelFactory)
where TEntityBaseModel : EntityViewModel
{
if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType))
throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory));
lock (this)
{
var results = new List<WeakEntityControlFactoryResult>();
_modelFactories.Add(modelFactory, results);
var i = 0;
while (i < _boundEntityViewModels.Count)
{
if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) &&
_boundViewModels.TryGetValue(target, out MtObservableList<EntityControlViewModel> components))
{
if (target is TEntityBaseModel tent)
{
EntityControlViewModel result = modelFactory.Invoke(tent);
if (result != null)
{
_log.Debug($"Model factory {modelFactory.Method} created {result} for {tent}");
components.Add(result);
results.Add(new WeakEntityControlFactoryResult(new WeakReference<EntityViewModel>(target), new WeakReference<EntityControlViewModel>(result)));
}
}
i++;
}
else
_boundEntityViewModels.RemoveAtFast(i);
}
}
}
/// <summary>
/// Unregisters a factory registered with <see cref="RegisterModelFactory{TEntityBaseModel}"/>
/// </summary>
/// <typeparam name="TEntityBaseModel">entity model type to match</typeparam>
/// <param name="modelFactory">Method to create component model from entity model.</param>
public void UnregisterModelFactory<TEntityBaseModel>(Func<TEntityBaseModel, EntityControlViewModel> modelFactory)
where TEntityBaseModel : EntityViewModel
{
if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType))
throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory));
lock (this)
{
if (!_modelFactories.TryGetValue(modelFactory, out var results))
return;
_modelFactories.Remove(modelFactory);
foreach (WeakEntityControlFactoryResult result in results)
{
if (result.Key.TryGetTarget(out EntityViewModel target) &&
result.Value.TryGetTarget(out EntityControlViewModel created)
&& _boundViewModels.TryGetValue(target, out MtObservableList<EntityControlViewModel> registered))
{
registered.Remove(created);
}
}
}
}
/// <summary>
/// This factory will be used to create controls for matching view models.
/// </summary>
/// <typeparam name="TEntityComponentModel">component model to match</typeparam>
/// <param name="controlFactory">Method to create control from component model</param>
public void RegisterControlFactory<TEntityComponentModel>(
Func<TEntityComponentModel, Control> controlFactory)
where TEntityComponentModel : EntityControlViewModel
{
if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType))
throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory));
lock (this)
{
_controlFactories.Add(controlFactory);
RefreshControls<TEntityComponentModel>();
}
}
///<summary>
/// Unregisters a factory registered with <see cref="RegisterControlFactory{TEntityComponentModel}"/>
/// </summary>
/// <typeparam name="TEntityComponentModel">component model to match</typeparam>
/// <param name="controlFactory">Method to create control from component model</param>
public void UnregisterControlFactory<TEntityComponentModel>(
Func<TEntityComponentModel, Control> controlFactory)
where TEntityComponentModel : EntityControlViewModel
{
if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType))
throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory));
lock (this)
{
_controlFactories.Remove(controlFactory);
RefreshControls<TEntityComponentModel>();
}
}
private void RefreshControls<TEntityComponentModel>() where TEntityComponentModel : EntityControlViewModel
{
var i = 0;
while (i < _boundEntityViewModels.Count)
{
if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) &&
_boundViewModels.TryGetValue(target, out MtObservableList<EntityControlViewModel> components))
{
foreach (EntityControlViewModel component in components)
if (component is TEntityComponentModel)
component.InvalidateControl();
i++;
}
else
_boundEntityViewModels.RemoveAtFast(i);
}
}
/// <summary>
/// Gets the models bound to the given entity view model.
/// </summary>
/// <param name="entity">view model to query</param>
/// <returns></returns>
public MtObservableList<EntityControlViewModel> BoundModels(EntityViewModel entity)
{
return _boundViewModels.GetValue(entity, CreateFreshBinding);
}
/// <summary>
/// Gets a control for the given view model type.
/// </summary>
/// <param name="model">model to create a control for</param>
/// <returns>control, or null if none</returns>
public Control CreateControl(EntityControlViewModel model)
{
lock (this)
foreach (Delegate factory in _controlFactories)
if (factory.Method.GetParameters()[0].ParameterType.IsInstanceOfType(model) &&
factory.DynamicInvoke(model) is Control result)
{
_log.Debug($"Control factory {factory.Method} created {result}");
return result;
}
_log.Warn($"No control created for {model}");
return null;
}
private MtObservableList<EntityControlViewModel> CreateFreshBinding(EntityViewModel key)
{
var binding = new MtObservableList<EntityControlViewModel>();
lock (this)
{
foreach (KeyValuePair<Delegate, List<WeakEntityControlFactoryResult>> factory in _modelFactories)
{
Type ptype = factory.Key.Method.GetParameters()[0].ParameterType;
if (ptype.IsInstanceOfType(key) &&
factory.Key.DynamicInvoke(key) is EntityControlViewModel result)
{
_log.Debug($"Model factory {factory.Key.Method} created {result} for {key}");
binding.Add(result);
result.InvalidateControl();
factory.Value.Add(new WeakEntityControlFactoryResult(new WeakReference<EntityViewModel>(key), new WeakReference<EntityControlViewModel>(result)));
}
}
_boundEntityViewModels.Add(new WeakReference<EntityViewModel>(key));
}
return binding;
}
}
}

View File

@@ -195,6 +195,7 @@
<Link>Properties\AssemblyVersion.cs</Link> <Link>Properties\AssemblyVersion.cs</Link>
</Compile> </Compile>
<Compile Include="ListBoxExtensions.cs" /> <Compile Include="ListBoxExtensions.cs" />
<Compile Include="Managers\EntityControlManager.cs" />
<Compile Include="Managers\MultiplayerManagerDedicated.cs" /> <Compile Include="Managers\MultiplayerManagerDedicated.cs" />
<Compile Include="Managers\InstanceManager.cs" /> <Compile Include="Managers\InstanceManager.cs" />
<Compile Include="NativeMethods.cs" /> <Compile Include="NativeMethods.cs" />
@@ -214,6 +215,13 @@
<Compile Include="ViewModels\Entities\Blocks\PropertyViewModel.cs" /> <Compile Include="ViewModels\Entities\Blocks\PropertyViewModel.cs" />
<Compile Include="ViewModels\Entities\CharacterViewModel.cs" /> <Compile Include="ViewModels\Entities\CharacterViewModel.cs" />
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" /> <Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
<Compile Include="ViewModels\Entities\EntityControlViewModel.cs" />
<Compile Include="Views\Entities\EntityControlHost.xaml.cs">
<DependentUpon>EntityControlHost.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Entities\EntityControlsView.xaml.cs">
<DependentUpon>EntityControlsView.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModels\EntityTreeViewModel.cs" /> <Compile Include="ViewModels\EntityTreeViewModel.cs" />
<Compile Include="ViewModels\Entities\EntityViewModel.cs" /> <Compile Include="ViewModels\Entities\EntityViewModel.cs" />
<Compile Include="ViewModels\Entities\FloatingObjectViewModel.cs" /> <Compile Include="ViewModels\Entities\FloatingObjectViewModel.cs" />
@@ -221,7 +229,6 @@
<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" />
@@ -262,9 +269,6 @@
<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>
@@ -309,6 +313,14 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Page Include="Views\Entities\EntityControlHost.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Entities\EntityControlsView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddWorkshopItemsDialog.xaml"> <Page Include="Views\AddWorkshopItemsDialog.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
@@ -337,6 +349,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\PluginsControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Entities\VoxelMapView.xaml"> <Page Include="Views\Entities\VoxelMapView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
@@ -349,14 +365,6 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\PluginsControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\ProfilerConfigControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\TorchUI.xaml"> <Page Include="Views\TorchUI.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>

View File

@@ -72,6 +72,7 @@ namespace Torch.Server
{ {
DedicatedInstance = new InstanceManager(this); DedicatedInstance = new InstanceManager(this);
AddManager(DedicatedInstance); AddManager(DedicatedInstance);
AddManager(new EntityControlManager(this));
Config = config ?? new TorchConfig(); Config = config ?? new TorchConfig();
var sessionManager = Managers.GetManager<ITorchSessionManager>(); var sessionManager = Managers.GetManager<ITorchSessionManager>();

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Torch.Server.ViewModels.Entities
{
public class EntityControlViewModel : ViewModel
{
internal const string SignalPropertyInvalidateControl =
"InvalidateControl-4124a476-704f-4762-8b5e-336a18e2f7e5";
internal void InvalidateControl()
{
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(SignalPropertyInvalidateControl);
}
private bool _hide;
/// <summary>
/// Should this element be forced into the <see cref="Visibility.Collapsed"/>
/// </summary>
public bool Hide
{
get => _hide;
protected set
{
if (_hide == value)
return;
_hide = value;
OnPropertyChanged();
}
}
}
}

View File

@@ -1,7 +1,7 @@
using System.Windows.Controls; using System.Windows.Controls;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.Collections; using Torch.Collections;
using Torch.Managers.Profiler; using Torch.Server.Managers;
using VRage.Game.ModAPI; using VRage.Game.ModAPI;
using VRage.ModAPI; using VRage.ModAPI;
using VRageMath; using VRageMath;
@@ -11,14 +11,24 @@ namespace Torch.Server.ViewModels.Entities
public class EntityViewModel : ViewModel public class EntityViewModel : ViewModel
{ {
protected EntityTreeViewModel Tree { get; } protected EntityTreeViewModel Tree { get; }
public IMyEntity Entity { get; }
public long Id => Entity.EntityId; private IMyEntity _backing;
public ProfilerEntryViewModel Profiler public IMyEntity Entity
{ {
get => ProfilerTreeAlias[0]; get => _backing;
set => ProfilerTreeAlias[0] = value; protected set
{
_backing = value;
OnPropertyChanged();
EntityControls = TorchBase.Instance?.Managers.GetManager<EntityControlManager>()?.BoundModels(this);
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(EntityControls));
} }
public MtObservableList<ProfilerEntryViewModel> ProfilerTreeAlias { get; } = new MtObservableList<ProfilerEntryViewModel>(1){null}; }
public long Id => Entity.EntityId;
public MtObservableList<EntityControlViewModel> EntityControls { get; private set; }
public virtual string Name public virtual string Name
{ {
@@ -56,7 +66,6 @@ namespace Torch.Server.ViewModels.Entities
{ {
Entity = entity; Entity = entity;
Tree = tree; Tree = tree;
Profiler = TorchBase.Instance.Managers.GetManager<ProfilerManager>()?.EntityData(entity, Profiler);
} }
public EntityViewModel() public EntityViewModel()

View File

@@ -4,7 +4,6 @@ using Sandbox.Game.Entities;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.Collections; using Torch.Collections;
using Torch.Managers.Profiler;
using Torch.Server.ViewModels.Blocks; using Torch.Server.ViewModels.Blocks;
namespace Torch.Server.ViewModels.Entities namespace Torch.Server.ViewModels.Entities

View File

@@ -17,6 +17,8 @@ namespace Torch.Server.ViewModels
{ {
public class EntityTreeViewModel : ViewModel public class EntityTreeViewModel : ViewModel
{ {
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
//TODO: these should be sorted sets for speed //TODO: these should be sorted sets for speed
public MtObservableList<GridViewModel> Grids { get; set; } = new MtObservableList<GridViewModel>(); public MtObservableList<GridViewModel> Grids { get; set; } = new MtObservableList<GridViewModel>();
public MtObservableList<CharacterViewModel> Characters { get; set; } = new MtObservableList<CharacterViewModel>(); public MtObservableList<CharacterViewModel> Characters { get; set; } = new MtObservableList<CharacterViewModel>();
@@ -45,6 +47,8 @@ namespace Torch.Server.ViewModels
} }
private void MyEntities_OnEntityRemove(VRage.Game.Entity.MyEntity obj) private void MyEntities_OnEntityRemove(VRage.Game.Entity.MyEntity obj)
{
try
{ {
switch (obj) switch (obj)
{ {
@@ -62,8 +66,16 @@ namespace Torch.Server.ViewModels
break; break;
} }
} }
catch (Exception e)
{
_log.Error(e);
// ignore error "it's only UI"
}
}
private void MyEntities_OnEntityAdd(VRage.Game.Entity.MyEntity obj) private void MyEntities_OnEntityAdd(VRage.Game.Entity.MyEntity obj)
{
try
{ {
switch (obj) switch (obj)
{ {
@@ -81,5 +93,11 @@ namespace Torch.Server.ViewModels
break; break;
} }
} }
catch (Exception e)
{
_log.Error(e);
// ignore error "it's only UI"
}
}
} }
} }

View File

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

View File

@@ -5,6 +5,8 @@
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.Blocks" xmlns:local="clr-namespace:Torch.Server.Views.Blocks"
xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks" xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks"
xmlns:entities="clr-namespace:Torch.Server.Views.Entities"
xmlns:entities1="clr-namespace:Torch.Server.ViewModels.Entities"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.DataContext> <UserControl.DataContext>
<blocks:BlockViewModel /> <blocks:BlockViewModel />
@@ -12,6 +14,7 @@
<Grid Margin="3"> <Grid Margin="3">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
<RowDefinition/> <RowDefinition/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel Grid.Row="0"> <StackPanel Grid.Row="0">
@@ -22,7 +25,8 @@
</StackPanel> </StackPanel>
<Label Content="Properties"/> <Label Content="Properties"/>
</StackPanel> </StackPanel>
<ListView Grid.Row="1" ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True"> <Expander Grid.Row="1" Header="Block Properties">
<ListView ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<local:PropertyView /> <local:PropertyView />
@@ -39,5 +43,9 @@
</Style> </Style>
</ListView.ItemContainerStyle> </ListView.ItemContainerStyle>
</ListView> </ListView>
</Expander>
<ScrollViewer Grid.Row="2" Margin="3" VerticalScrollBarVisibility="Auto">
<entities:EntityControlsView DataContext="{Binding}"/>
</ScrollViewer>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -0,0 +1,8 @@
<UserControl x:Class="Torch.Server.Views.Entities.EntityControlHost"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
</UserControl>

View File

@@ -0,0 +1,60 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Torch.Server.Managers;
using Torch.API.Managers;
using Torch.Server.ViewModels.Entities;
namespace Torch.Server.Views.Entities
{
/// <summary>
/// Interaction logic for EntityControlHost.xaml
/// </summary>
public partial class EntityControlHost : UserControl
{
public EntityControlHost()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is ViewModel vmo)
{
vmo.PropertyChanged -= DataContext_OnPropertyChanged;
}
if (e.NewValue is ViewModel vmn)
{
vmn.PropertyChanged += DataContext_OnPropertyChanged;
}
RefreshControl();
}
private void DataContext_OnPropertyChanged(object sender, PropertyChangedEventArgs pa)
{
if (pa.PropertyName.Equals(EntityControlViewModel.SignalPropertyInvalidateControl))
RefreshControl();
else if (pa.PropertyName.Equals(nameof(EntityControlViewModel.Hide)))
RefreshVisibility();
}
private Control _currentControl;
private void RefreshControl()
{
_currentControl = DataContext is EntityControlViewModel ecvm
? TorchBase.Instance?.Managers.GetManager<EntityControlManager>()?.CreateControl(ecvm)
: null;
Content = _currentControl;
RefreshVisibility();
}
private void RefreshVisibility()
{
Visibility = (DataContext is EntityControlViewModel ecvm) && !ecvm.Hide && _currentControl != null
? Visibility.Visible
: Visibility.Collapsed;
}
}
}

View File

@@ -0,0 +1,31 @@
<ItemsControl x:Class="Torch.Server.Views.Entities.EntityControlsView"
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:entities="clr-namespace:Torch.Server.Views.Entities"
xmlns:modelsEntities="clr-namespace:Torch.Server.ViewModels.Entities"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding EntityControls}">
<ItemsControl.DataContext>
<modelsEntities:EntityViewModel/>
</ItemsControl.DataContext>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<entities:EntityControlHost DataContext="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Control.VerticalContentAlignment" Value="Stretch"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Torch.Server.Views.Entities
{
/// <summary>
/// Interaction logic for EntityControlsView.xaml
/// </summary>
public partial class EntityControlsView : ItemsControl
{
public EntityControlsView()
{
InitializeComponent();
}
}
}

View File

@@ -3,30 +3,28 @@
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:profiler="clr-namespace:Torch.Managers.Profiler;assembly=Torch"
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities" xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
xmlns:local="clr-namespace:Torch.Server.Views.Entities"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.DataContext> <UserControl.DataContext>
<entities:GridViewModel /> <entities:GridViewModel />
</UserControl.DataContext> </UserControl.DataContext>
<StackPanel> <Grid>
<StackPanel Orientation="Horizontal"> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label Content="Name" Width="100"/> <Label Content="Name" Width="100"/>
<TextBox Text="{Binding Name}" Margin="3"/> <TextBox Text="{Binding Name}" Margin="3"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal"> <StackPanel Grid.Row="1" Orientation="Horizontal">
<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"> <ScrollViewer Grid.Row="2" Margin="3" VerticalScrollBarVisibility="Auto">
<TreeView.Resources> <local:EntityControlsView DataContext="{Binding}"/>
<HierarchicalDataTemplate DataType="{x:Type profiler:ProfilerEntryViewModel}" ItemsSource="{Binding Children}"> </ScrollViewer>
<StackPanel Orientation="Horizontal"> </Grid>
<TextBlock Text="{Binding OwnerName}" Margin="3" MinWidth="200"/>
<TextBlock Text="{Binding UpdateTimeMs, StringFormat=' {0:F4} ms'}" Margin="3" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel>
</UserControl> </UserControl>

View File

@@ -9,8 +9,12 @@
<UserControl.DataContext> <UserControl.DataContext>
<entities:VoxelMapViewModel/> <entities:VoxelMapViewModel/>
</UserControl.DataContext> </UserControl.DataContext>
<StackPanel> <Grid>
<Label Content="Attached Grids"></Label> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Expander Grid.Row="0" Header="Attached Grids">
<ListView ItemsSource="{Binding AttachedGrids}" Margin="3"> <ListView ItemsSource="{Binding AttachedGrids}" Margin="3">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
@@ -18,5 +22,10 @@
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
</StackPanel> </Expander>
<ScrollViewer Grid.Row="1" Margin="3" VerticalScrollBarVisibility="Auto">
<local:EntityControlsView DataContext="{Binding}"/>
</ScrollViewer>
</Grid>
</UserControl> </UserControl>

View File

@@ -57,10 +57,10 @@ namespace Torch.Server
switch (newState) switch (newState)
{ {
case TorchSessionState.Loaded: case TorchSessionState.Loaded:
Dispatcher.Invoke(() => DataContext = _server?.CurrentSession?.Managers.GetManager<MultiplayerManagerDedicated>()); Dispatcher.InvokeAsync(() => DataContext = _server?.CurrentSession?.Managers.GetManager<MultiplayerManagerDedicated>());
break; break;
case TorchSessionState.Unloading: case TorchSessionState.Unloading:
Dispatcher.Invoke(() => DataContext = null); Dispatcher.InvokeAsync(() => DataContext = null);
break; break;
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ namespace Torch.Collections
/// </summary> /// </summary>
/// <typeparam name="TEvtArgs">Event argument type</typeparam> /// <typeparam name="TEvtArgs">Event argument type</typeparam>
/// <typeparam name="TEvtHandle">Event handler delegate type</typeparam> /// <typeparam name="TEvtHandle">Event handler delegate type</typeparam>
internal sealed class MtObservableEvent<TEvtArgs, TEvtHandle> where TEvtArgs : EventArgs public sealed class MtObservableEvent<TEvtArgs, TEvtHandle> where TEvtArgs : EventArgs
{ {
private delegate void DelInvokeHandler(TEvtHandle handler, object sender, TEvtArgs args); private delegate void DelInvokeHandler(TEvtHandle handler, object sender, TEvtArgs args);
@@ -28,19 +28,32 @@ namespace Torch.Collections
private event EventHandler<TEvtArgs> Event; private event EventHandler<TEvtArgs> Event;
internal void Raise(object sender, TEvtArgs args) /// <summary>
/// Raises this event for the given sender, with the given args
/// </summary>
/// <param name="sender">sender</param>
/// <param name="args">args</param>
public void Raise(object sender, TEvtArgs args)
{ {
Event?.Invoke(sender, args); Event?.Invoke(sender, args);
} }
internal void Add(TEvtHandle evt) /// <summary>
/// Adds the given event handler.
/// </summary>
/// <param name="evt"></param>
public void Add(TEvtHandle evt)
{ {
if (evt == null) if (evt == null)
return; return;
Event += new DispatcherDelegate(evt).Invoke; Event += new DispatcherDelegate(evt).Invoke;
} }
internal void Remove(TEvtHandle evt) /// <summary>
/// Removes the given event handler
/// </summary>
/// <param name="evt"></param>
public void Remove(TEvtHandle evt)
{ {
if (Event == null || evt == null) if (Event == null || evt == null)
return; return;

View File

@@ -148,12 +148,20 @@ namespace Torch.Managers.PatchManager
return count; return count;
} }
/// <inheritdoc cref="Commit"/> /// <inheritdoc cref="Commit"/>
internal static void CommitInternal() internal static void CommitInternal()
{ {
lock (_rewritePatterns) lock (_rewritePatterns)
{
#if true
ParallelTasks.Parallel.ForEach(_rewritePatterns.Values, x => x.Commit());
#else
foreach (DecoratedMethod m in _rewritePatterns.Values) foreach (DecoratedMethod m in _rewritePatterns.Values)
m.Commit(); m.Commit();
#endif
}
} }
/// <summary> /// <summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,7 +44,7 @@ namespace Torch
path = Path; path = Path;
var ser = new XmlSerializer(typeof(T)); var ser = new XmlSerializer(typeof(T));
using (var f = File.Create(path)) using (var f = File.CreateText(path))
{ {
ser.Serialize(f, Data); ser.Serialize(f, Data);
} }
@@ -57,7 +57,7 @@ namespace Torch
if (File.Exists(path)) if (File.Exists(path))
{ {
var ser = new XmlSerializer(typeof(T)); var ser = new XmlSerializer(typeof(T));
using (var f = File.OpenRead(path)) using (var f = File.OpenText(path))
{ {
config.Data = (T)ser.Deserialize(f); config.Data = (T)ser.Deserialize(f);
} }

View File

@@ -17,6 +17,7 @@ using Torch.API.Plugins;
using Torch.API.Session; using Torch.API.Session;
using Torch.Collections; using Torch.Collections;
using Torch.Commands; using Torch.Commands;
using Torch.Utils;
namespace Torch.Managers namespace Torch.Managers
{ {
@@ -235,11 +236,27 @@ namespace Torch.Managers
if (!file.Contains(".dll", StringComparison.CurrentCultureIgnoreCase)) if (!file.Contains(".dll", StringComparison.CurrentCultureIgnoreCase))
continue; continue;
if (false) {
var asm = Assembly.LoadFrom(file);
assemblies.Add(asm);
TorchBase.RegisterAuxAssembly(asm);
continue;
}
using (var stream = File.OpenRead(file)) using (var stream = File.OpenRead(file))
{ {
var data = new byte[stream.Length]; var data = stream.ReadToEnd();
stream.Read(data, 0, data.Length); #if DEBUG
byte[] symbol = null;
var symbolPath = Path.Combine(Path.GetDirectoryName(file) ?? ".",
Path.GetFileNameWithoutExtension(file) + ".pdb");
if (File.Exists(symbolPath))
using (var symbolStream = File.OpenRead(symbolPath))
symbol = symbolStream.ReadToEnd();
Assembly asm = symbol != null ? Assembly.Load(data, symbol) : Assembly.Load(data);
#else
Assembly asm = Assembly.Load(data); Assembly asm = Assembly.Load(data);
#endif
assemblies.Add(asm); assemblies.Add(asm);
TorchBase.RegisterAuxAssembly(asm); TorchBase.RegisterAuxAssembly(asm);
} }
@@ -268,8 +285,7 @@ namespace Torch.Managers
using (var stream = entry.Open()) using (var stream = entry.Open())
{ {
var data = new byte[entry.Length]; var data = stream.ReadToEnd();
stream.Read(data, 0, data.Length);
Assembly asm = Assembly.Load(data); Assembly asm = Assembly.Load(data);
TorchBase.RegisterAuxAssembly(asm); TorchBase.RegisterAuxAssembly(asm);
} }

View File

@@ -193,13 +193,6 @@
<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="Patches\RegisterFromCallingAssemblyPatch.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
@@ -226,6 +219,7 @@
<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\MiscExtensions.cs" />
<Compile Include="Utils\Reflected\ReflectedEventReplaceAttribute.cs" /> <Compile Include="Utils\Reflected\ReflectedEventReplaceAttribute.cs" />
<Compile Include="Utils\Reflected\ReflectedEventReplacer.cs" /> <Compile Include="Utils\Reflected\ReflectedEventReplacer.cs" />
<Compile Include="Utils\Reflected\ReflectedFieldInfoAttribute.cs" /> <Compile Include="Utils\Reflected\ReflectedFieldInfoAttribute.cs" />

View File

@@ -27,7 +27,6 @@ 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;
@@ -138,7 +137,6 @@ 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;
} }

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Torch.Utils
{
public static class MiscExtensions
{
private static readonly ThreadLocal<WeakReference<byte[]>> _streamBuffer = new ThreadLocal<WeakReference<byte[]>>(() => new WeakReference<byte[]>(null));
public static byte[] ReadToEnd(this Stream stream)
{
byte[] buffer;
if (!_streamBuffer.Value.TryGetTarget(out buffer))
buffer = new byte[stream.Length];
if (buffer.Length < stream.Length)
buffer = new byte[stream.Length];
if (buffer.Length < 1024)
buffer = new byte[1024];
while (true)
{
if (buffer.Length == stream.Position)
Array.Resize(ref buffer, Math.Max((int)stream.Length, buffer.Length * 2));
int count = stream.Read(buffer, (int)stream.Position, buffer.Length - (int)stream.Position);
if (count == 0)
break;
}
var result = new byte[(int)stream.Position];
Array.Copy(buffer, 0, result, 0, result.Length);
_streamBuffer.Value.SetTarget(buffer);
return result;
}
}
}