Pluggable system for providing WPF controls inside the Entity Manager
This commit is contained in:
@@ -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>
|
||||||
|
214
Torch.Server/Managers/EntityControlManager.cs
Normal file
214
Torch.Server/Managers/EntityControlManager.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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>
|
||||||
|
@@ -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>();
|
||||||
|
38
Torch.Server/ViewModels/Entities/EntityControlViewModel.cs
Normal file
38
Torch.Server/ViewModels/Entities/EntityControlViewModel.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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()
|
||||||
|
@@ -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
|
||||||
|
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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>
|
8
Torch.Server/Views/Entities/EntityControlHost.xaml
Normal file
8
Torch.Server/Views/Entities/EntityControlHost.xaml
Normal 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>
|
60
Torch.Server/Views/Entities/EntityControlHost.xaml.cs
Normal file
60
Torch.Server/Views/Entities/EntityControlHost.xaml.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
Torch.Server/Views/Entities/EntityControlsView.xaml
Normal file
31
Torch.Server/Views/Entities/EntityControlsView.xaml
Normal 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>
|
15
Torch.Server/Views/Entities/EntityControlsView.xaml.cs
Normal file
15
Torch.Server/Views/Entities/EntityControlsView.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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>
|
@@ -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>
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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>
|
|
@@ -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>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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>
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
@@ -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>
|
||||||
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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" />
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
38
Torch/Utils/MiscExtensions.cs
Normal file
38
Torch/Utils/MiscExtensions.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user