diff --git a/NLog.config b/NLog.config index 2b900e3..abf43fd 100644 --- a/NLog.config +++ b/NLog.config @@ -18,7 +18,7 @@ - + diff --git a/Torch.Server/Managers/EntityControlManager.cs b/Torch.Server/Managers/EntityControlManager.cs new file mode 100644 index 0000000..308cb61 --- /dev/null +++ b/Torch.Server/Managers/EntityControlManager.cs @@ -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>; + +namespace Torch.Server.Managers +{ + /// + /// Manager that lets users bind random view models to entities in Torch's Entity Manager + /// + public class EntityControlManager : Manager + { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + /// + /// Creates an entity control manager for the given instance of torch + /// + /// Torch instance + internal EntityControlManager(ITorchBase torchInstance) : base(torchInstance) + { + } + + private readonly Dictionary> _modelFactories = new Dictionary>(); + private readonly List _controlFactories = new List(); + + private readonly List> _boundEntityViewModels = new List>(); + private readonly ConditionalWeakTable> _boundViewModels = new ConditionalWeakTable>(); + + /// + /// This factory will be used to create component models for matching entity models. + /// + /// entity model type to match + /// Method to create component model from entity model. + public void RegisterModelFactory(Func 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(); + _modelFactories.Add(modelFactory, results); + + var i = 0; + while (i < _boundEntityViewModels.Count) + { + if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) && + _boundViewModels.TryGetValue(target, out MtObservableList 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(target), new WeakReference(result))); + } + } + i++; + } + else + _boundEntityViewModels.RemoveAtFast(i); + } + } + } + + /// + /// Unregisters a factory registered with + /// + /// entity model type to match + /// Method to create component model from entity model. + public void UnregisterModelFactory(Func 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 registered)) + { + registered.Remove(created); + } + } + } + } + + /// + /// This factory will be used to create controls for matching view models. + /// + /// component model to match + /// Method to create control from component model + public void RegisterControlFactory( + Func 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(); + } + } + + /// + /// Unregisters a factory registered with + /// + /// component model to match + /// Method to create control from component model + public void UnregisterControlFactory( + Func 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(); + } + } + + private void RefreshControls() where TEntityComponentModel : EntityControlViewModel + { + var i = 0; + while (i < _boundEntityViewModels.Count) + { + if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) && + _boundViewModels.TryGetValue(target, out MtObservableList components)) + { + foreach (EntityControlViewModel component in components) + if (component is TEntityComponentModel) + component.InvalidateControl(); + i++; + } + else + _boundEntityViewModels.RemoveAtFast(i); + } + } + + /// + /// Gets the models bound to the given entity view model. + /// + /// view model to query + /// + public MtObservableList BoundModels(EntityViewModel entity) + { + return _boundViewModels.GetValue(entity, CreateFreshBinding); + } + + /// + /// Gets a control for the given view model type. + /// + /// model to create a control for + /// control, or null if none + 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 CreateFreshBinding(EntityViewModel key) + { + var binding = new MtObservableList(); + lock (this) + { + foreach (KeyValuePair> 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(key), new WeakReference(result))); + } + } + _boundEntityViewModels.Add(new WeakReference(key)); + } + return binding; + } + } +} diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj index d632099..ea5c705 100644 --- a/Torch.Server/Torch.Server.csproj +++ b/Torch.Server/Torch.Server.csproj @@ -195,6 +195,7 @@ Properties\AssemblyVersion.cs + @@ -214,6 +215,13 @@ + + + EntityControlHost.xaml + + + EntityControlsView.xaml + @@ -221,7 +229,6 @@ - @@ -262,9 +269,6 @@ PluginsControl.xaml - - ProfilerConfigControl.xaml - TorchUI.xaml @@ -309,6 +313,14 @@ + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -337,6 +349,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -349,14 +365,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - MSBuild:Compile Designer diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index 44729df..ee8d1a5 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -72,6 +72,7 @@ namespace Torch.Server { DedicatedInstance = new InstanceManager(this); AddManager(DedicatedInstance); + AddManager(new EntityControlManager(this)); Config = config ?? new TorchConfig(); var sessionManager = Managers.GetManager(); diff --git a/Torch.Server/ViewModels/Entities/EntityControlViewModel.cs b/Torch.Server/ViewModels/Entities/EntityControlViewModel.cs new file mode 100644 index 0000000..3258365 --- /dev/null +++ b/Torch.Server/ViewModels/Entities/EntityControlViewModel.cs @@ -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; + + /// + /// Should this element be forced into the + /// + public bool Hide + { + get => _hide; + protected set + { + if (_hide == value) + return; + _hide = value; + OnPropertyChanged(); + } + } + } +} diff --git a/Torch.Server/ViewModels/Entities/EntityViewModel.cs b/Torch.Server/ViewModels/Entities/EntityViewModel.cs index f9b8dcd..05eabbc 100644 --- a/Torch.Server/ViewModels/Entities/EntityViewModel.cs +++ b/Torch.Server/ViewModels/Entities/EntityViewModel.cs @@ -1,7 +1,7 @@ using System.Windows.Controls; using Torch.API.Managers; using Torch.Collections; -using Torch.Managers.Profiler; +using Torch.Server.Managers; using VRage.Game.ModAPI; using VRage.ModAPI; using VRageMath; @@ -11,14 +11,24 @@ namespace Torch.Server.ViewModels.Entities public class EntityViewModel : ViewModel { protected EntityTreeViewModel Tree { get; } - public IMyEntity Entity { get; } - public long Id => Entity.EntityId; - public ProfilerEntryViewModel Profiler + + private IMyEntity _backing; + public IMyEntity Entity { - get => ProfilerTreeAlias[0]; - set => ProfilerTreeAlias[0] = value; + get => _backing; + protected set + { + _backing = value; + OnPropertyChanged(); + EntityControls = TorchBase.Instance?.Managers.GetManager()?.BoundModels(this); + // ReSharper disable once ExplicitCallerInfoArgument + OnPropertyChanged(nameof(EntityControls)); + } } - public MtObservableList ProfilerTreeAlias { get; } = new MtObservableList(1){null}; + + public long Id => Entity.EntityId; + + public MtObservableList EntityControls { get; private set; } public virtual string Name { @@ -56,7 +66,6 @@ namespace Torch.Server.ViewModels.Entities { Entity = entity; Tree = tree; - Profiler = TorchBase.Instance.Managers.GetManager()?.EntityData(entity, Profiler); } public EntityViewModel() diff --git a/Torch.Server/ViewModels/Entities/GridViewModel.cs b/Torch.Server/ViewModels/Entities/GridViewModel.cs index 9cbd2cc..8d06f26 100644 --- a/Torch.Server/ViewModels/Entities/GridViewModel.cs +++ b/Torch.Server/ViewModels/Entities/GridViewModel.cs @@ -4,7 +4,6 @@ using Sandbox.Game.Entities; using Sandbox.ModAPI; using Torch.API.Managers; using Torch.Collections; -using Torch.Managers.Profiler; using Torch.Server.ViewModels.Blocks; namespace Torch.Server.ViewModels.Entities diff --git a/Torch.Server/ViewModels/EntityTreeViewModel.cs b/Torch.Server/ViewModels/EntityTreeViewModel.cs index 87b00cf..76de7da 100644 --- a/Torch.Server/ViewModels/EntityTreeViewModel.cs +++ b/Torch.Server/ViewModels/EntityTreeViewModel.cs @@ -17,6 +17,8 @@ namespace Torch.Server.ViewModels { public class EntityTreeViewModel : ViewModel { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + //TODO: these should be sorted sets for speed public MtObservableList Grids { get; set; } = new MtObservableList(); public MtObservableList Characters { get; set; } = new MtObservableList(); @@ -46,39 +48,55 @@ namespace Torch.Server.ViewModels private void MyEntities_OnEntityRemove(VRage.Game.Entity.MyEntity obj) { - switch (obj) + try { - case MyCubeGrid grid: - Grids.RemoveWhere(m => m.Id == grid.EntityId); - break; - case MyCharacter character: - Characters.RemoveWhere(m => m.Id == character.EntityId); - break; - case MyFloatingObject floating: - FloatingObjects.RemoveWhere(m => m.Id == floating.EntityId); - break; - case MyVoxelBase voxel: - VoxelMaps.RemoveWhere(m => m.Id == voxel.EntityId); - break; + switch (obj) + { + case MyCubeGrid grid: + Grids.RemoveWhere(m => m.Id == grid.EntityId); + break; + case MyCharacter character: + Characters.RemoveWhere(m => m.Id == character.EntityId); + break; + case MyFloatingObject floating: + FloatingObjects.RemoveWhere(m => m.Id == floating.EntityId); + break; + case MyVoxelBase voxel: + VoxelMaps.RemoveWhere(m => m.Id == voxel.EntityId); + break; + } + } + catch (Exception e) + { + _log.Error(e); + // ignore error "it's only UI" } } private void MyEntities_OnEntityAdd(VRage.Game.Entity.MyEntity obj) { - switch (obj) + try { - case MyCubeGrid grid: - Grids.Add(new GridViewModel(grid, this)); - break; - case MyCharacter character: - Characters.Add(new CharacterViewModel(character, this)); - break; - case MyFloatingObject floating: - FloatingObjects.Add(new FloatingObjectViewModel(floating, this)); - break; - case MyVoxelBase voxel: - VoxelMaps.Add(new VoxelMapViewModel(voxel, this)); - break; + switch (obj) + { + case MyCubeGrid grid: + Grids.Add(new GridViewModel(grid, this)); + break; + case MyCharacter character: + Characters.Add(new CharacterViewModel(character, this)); + break; + case MyFloatingObject floating: + FloatingObjects.Add(new FloatingObjectViewModel(floating, this)); + break; + case MyVoxelBase voxel: + VoxelMaps.Add(new VoxelMapViewModel(voxel, this)); + break; + } + } + catch (Exception e) + { + _log.Error(e); + // ignore error "it's only UI" } } } diff --git a/Torch.Server/ViewModels/ProfilerViewModel.cs b/Torch.Server/ViewModels/ProfilerViewModel.cs deleted file mode 100644 index 247f6c8..0000000 --- a/Torch.Server/ViewModels/ProfilerViewModel.cs +++ /dev/null @@ -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 ProfilerTreeAlias { get; } = new MtObservableList(); - - private readonly ProfilerManager _manager; - - public ProfilerViewModel() - { - _manager = null; - } - - public ProfilerViewModel(ProfilerManager profilerManager) - { - _manager = profilerManager; - ProfilerTreeAlias.Add(_manager.SessionData()); - ProfilerTreeAlias.Add(_manager.EntitiesData()); - } - - /// - public bool ProfileGridsUpdate - { - get => _manager?.ProfileGridsUpdate ?? false; - set - { - if (_manager != null) - _manager.ProfileGridsUpdate = value; - OnPropertyChanged(); - } - } - - /// - public bool ProfileBlocksUpdate - { - get => _manager?.ProfileBlocksUpdate ?? false; - set - { - if (_manager != null) - _manager.ProfileBlocksUpdate = value; - OnPropertyChanged(); - } - } - - /// - public bool ProfileEntityComponentsUpdate - { - get => _manager?.ProfileEntityComponentsUpdate ?? false; - set - { - if (_manager != null) - _manager.ProfileEntityComponentsUpdate = value; - OnPropertyChanged(); - } - } - - /// - public bool ProfileGridSystemUpdates - { - get => _manager?.ProfileGridSystemUpdates ?? false; - set - { - if (_manager != null) - _manager.ProfileGridSystemUpdates = value; - OnPropertyChanged(); - } - } - - /// - public bool ProfileSessionComponentsUpdate - { - get => _manager?.ProfileSessionComponentsUpdate ?? false; - set => _manager.ProfileSessionComponentsUpdate = value; - } - } -} diff --git a/Torch.Server/Views/Entities/Blocks/BlockView.xaml b/Torch.Server/Views/Entities/Blocks/BlockView.xaml index 8020097..53cca60 100644 --- a/Torch.Server/Views/Entities/Blocks/BlockView.xaml +++ b/Torch.Server/Views/Entities/Blocks/BlockView.xaml @@ -5,6 +5,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Torch.Server.Views.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"> @@ -12,6 +14,7 @@ + @@ -22,22 +25,27 @@ \ No newline at end of file diff --git a/Torch.Server/Views/Entities/EntityControlHost.xaml b/Torch.Server/Views/Entities/EntityControlHost.xaml new file mode 100644 index 0000000..a7b11af --- /dev/null +++ b/Torch.Server/Views/Entities/EntityControlHost.xaml @@ -0,0 +1,8 @@ + + diff --git a/Torch.Server/Views/Entities/EntityControlHost.xaml.cs b/Torch.Server/Views/Entities/EntityControlHost.xaml.cs new file mode 100644 index 0000000..bea6ada --- /dev/null +++ b/Torch.Server/Views/Entities/EntityControlHost.xaml.cs @@ -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 +{ + /// + /// Interaction logic for EntityControlHost.xaml + /// + 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()?.CreateControl(ecvm) + : null; + Content = _currentControl; + RefreshVisibility(); + } + + private void RefreshVisibility() + { + Visibility = (DataContext is EntityControlViewModel ecvm) && !ecvm.Hide && _currentControl != null + ? Visibility.Visible + : Visibility.Collapsed; + } + } +} diff --git a/Torch.Server/Views/Entities/EntityControlsView.xaml b/Torch.Server/Views/Entities/EntityControlsView.xaml new file mode 100644 index 0000000..9f4fba1 --- /dev/null +++ b/Torch.Server/Views/Entities/EntityControlsView.xaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/Torch.Server/Views/Entities/EntityControlsView.xaml.cs b/Torch.Server/Views/Entities/EntityControlsView.xaml.cs new file mode 100644 index 0000000..b490a5a --- /dev/null +++ b/Torch.Server/Views/Entities/EntityControlsView.xaml.cs @@ -0,0 +1,15 @@ +using System.Windows.Controls; + +namespace Torch.Server.Views.Entities +{ + /// + /// Interaction logic for EntityControlsView.xaml + /// + public partial class EntityControlsView : ItemsControl + { + public EntityControlsView() + { + InitializeComponent(); + } + } +} diff --git a/Torch.Server/Views/Entities/GridView.xaml b/Torch.Server/Views/Entities/GridView.xaml index 36620ee..f183117 100644 --- a/Torch.Server/Views/Entities/GridView.xaml +++ b/Torch.Server/Views/Entities/GridView.xaml @@ -3,30 +3,28 @@ 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:profiler="clr-namespace:Torch.Managers.Profiler;assembly=Torch" xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities" + xmlns:local="clr-namespace:Torch.Server.Views.Entities" mc:Ignorable="d"> - - + + + + + + + - + - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/Torch.Server/Views/Entities/VoxelMapView.xaml b/Torch.Server/Views/Entities/VoxelMapView.xaml index 4333edf..3c0409b 100644 --- a/Torch.Server/Views/Entities/VoxelMapView.xaml +++ b/Torch.Server/Views/Entities/VoxelMapView.xaml @@ -9,14 +9,23 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/Torch.Server/Views/PlayerListControl.xaml.cs b/Torch.Server/Views/PlayerListControl.xaml.cs index a920eb3..49c40a7 100644 --- a/Torch.Server/Views/PlayerListControl.xaml.cs +++ b/Torch.Server/Views/PlayerListControl.xaml.cs @@ -57,10 +57,10 @@ namespace Torch.Server switch (newState) { case TorchSessionState.Loaded: - Dispatcher.Invoke(() => DataContext = _server?.CurrentSession?.Managers.GetManager()); + Dispatcher.InvokeAsync(() => DataContext = _server?.CurrentSession?.Managers.GetManager()); break; case TorchSessionState.Unloading: - Dispatcher.Invoke(() => DataContext = null); + Dispatcher.InvokeAsync(() => DataContext = null); break; } } diff --git a/Torch.Server/Views/ProfilerConfigControl.xaml b/Torch.Server/Views/ProfilerConfigControl.xaml deleted file mode 100644 index b90876f..0000000 --- a/Torch.Server/Views/ProfilerConfigControl.xaml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Torch.Server/Views/ProfilerConfigControl.xaml.cs b/Torch.Server/Views/ProfilerConfigControl.xaml.cs deleted file mode 100644 index 8ecb7bd..0000000 --- a/Torch.Server/Views/ProfilerConfigControl.xaml.cs +++ /dev/null @@ -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 -{ - /// - /// Interaction logic for ProfilerControl.xaml - /// - public partial class ProfilerControl : UserControl - { - public ProfilerControl() - { - InitializeComponent(); - } - - public void BindServer(TorchServer server) - { - DataContext = new ProfilerViewModel(server.Managers.GetManager()); - } - } -} diff --git a/Torch.Server/Views/TorchUI.xaml b/Torch.Server/Views/TorchUI.xaml index 97729f7..1848f6c 100644 --- a/Torch.Server/Views/TorchUI.xaml +++ b/Torch.Server/Views/TorchUI.xaml @@ -69,9 +69,6 @@ - - - diff --git a/Torch.Server/Views/TorchUI.xaml.cs b/Torch.Server/Views/TorchUI.xaml.cs index 6e7716d..92b1083 100644 --- a/Torch.Server/Views/TorchUI.xaml.cs +++ b/Torch.Server/Views/TorchUI.xaml.cs @@ -48,7 +48,6 @@ namespace Torch.Server Chat.BindServer(server); PlayerList.BindServer(server); Plugins.BindServer(server); - Profiler.BindServer(server); LoadConfig((TorchConfig)server.Config); } diff --git a/Torch/Collections/MtObservableEvent.cs b/Torch/Collections/MtObservableEvent.cs index cc27c6b..c3ef257 100644 --- a/Torch/Collections/MtObservableEvent.cs +++ b/Torch/Collections/MtObservableEvent.cs @@ -11,7 +11,7 @@ namespace Torch.Collections /// /// Event argument type /// Event handler delegate type - internal sealed class MtObservableEvent where TEvtArgs : EventArgs + public sealed class MtObservableEvent where TEvtArgs : EventArgs { private delegate void DelInvokeHandler(TEvtHandle handler, object sender, TEvtArgs args); @@ -28,19 +28,32 @@ namespace Torch.Collections private event EventHandler Event; - internal void Raise(object sender, TEvtArgs args) + /// + /// Raises this event for the given sender, with the given args + /// + /// sender + /// args + public void Raise(object sender, TEvtArgs args) { Event?.Invoke(sender, args); } - internal void Add(TEvtHandle evt) + /// + /// Adds the given event handler. + /// + /// + public void Add(TEvtHandle evt) { if (evt == null) return; Event += new DispatcherDelegate(evt).Invoke; } - internal void Remove(TEvtHandle evt) + /// + /// Removes the given event handler + /// + /// + public void Remove(TEvtHandle evt) { if (Event == null || evt == null) return; diff --git a/Torch/Managers/PatchManager/PatchManager.cs b/Torch/Managers/PatchManager/PatchManager.cs index fce9991..bcc90c4 100644 --- a/Torch/Managers/PatchManager/PatchManager.cs +++ b/Torch/Managers/PatchManager/PatchManager.cs @@ -148,12 +148,20 @@ namespace Torch.Managers.PatchManager return count; } + /// internal static void CommitInternal() { lock (_rewritePatterns) - foreach (DecoratedMethod m in _rewritePatterns.Values) - m.Commit(); + { +#if true + ParallelTasks.Parallel.ForEach(_rewritePatterns.Values, x => x.Commit()); +#else + foreach (DecoratedMethod m in _rewritePatterns.Values) + m.Commit(); +#endif + + } } /// diff --git a/Torch/Managers/Profiler/FatProfilerEntry.cs b/Torch/Managers/Profiler/FatProfilerEntry.cs deleted file mode 100644 index 6479a96..0000000 --- a/Torch/Managers/Profiler/FatProfilerEntry.cs +++ /dev/null @@ -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.CreateValueCallback - ChildUpdateTimeCreateValueFat; - private readonly ConditionalWeakTable.CreateValueCallback - ChildUpdateTimeCreateValueSlim; - internal readonly ConditionalWeakTable ChildUpdateTime = new ConditionalWeakTable(); - - 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(result)); - return result; - }; - ChildUpdateTimeCreateValueSlim = (key) => - { - var result = new SlimProfilerEntry(this); - lock (ProfilerData.ProfilingEntriesAll) - ProfilerData.ProfilingEntriesAll.Add(new WeakReference(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); - } - } -} \ No newline at end of file diff --git a/Torch/Managers/Profiler/ProfilerData.cs b/Torch/Managers/Profiler/ProfilerData.cs deleted file mode 100644 index f04b220..0000000 --- a/Torch/Managers/Profiler/ProfilerData.cs +++ /dev/null @@ -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 -{ - /// - /// Indicates a "fixed" profiler entry. These always exist and will not be moved. - /// - internal enum ProfilerFixedEntry - { - Entities = 0, - Session = 1, - Count = 2 - } - - /// - /// Class that stores all the timing associated with the profiler. Use for observable views into this data. - /// - 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(cache = new ProfilerEntryViewModel())); - return cache; - } - - internal static readonly List> BoundViewModels = new List>(); - - #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> ProfilingEntriesAll = new List>(); - 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(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 - } -} diff --git a/Torch/Managers/Profiler/ProfilerEntryViewModel.cs b/Torch/Managers/Profiler/ProfilerEntryViewModel.cs deleted file mode 100644 index 085179a..0000000 --- a/Torch/Managers/Profiler/ProfilerEntryViewModel.cs +++ /dev/null @@ -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, ICollection> _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 Children { get; } = new MtObservableList(); - - private ProfilerFixedEntry _fixedEntry = ProfilerFixedEntry.Count; - private readonly WeakReference _owner = new WeakReference(null); - private WeakReference[] _getterExtra; - private Func _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[0]; - _getter = GetterImplEntity; - } - - internal void SetTarget(MyEntityComponentBase owner) - { - _fixedEntry = ProfilerFixedEntry.Count; - _owner.SetTarget(owner); - _getterExtra = new WeakReference[0]; - _getter = GetterImplEntityComponent; - } - - internal void SetTarget(IMyCubeGrid grid, object owner) - { - _fixedEntry = ProfilerFixedEntry.Count; - _owner.SetTarget(owner); - _getterExtra = new[] { new WeakReference(grid) }; - _getter = GetterImplGridSystem; - } - #endregion - - /// - /// Called to update the values of this view model without changing the target. - /// - /// False if the target was lost - internal bool Update() - { - object owner; - if (_fixedEntry == ProfilerFixedEntry.Count) - { - bool lostHandle = !_owner.TryGetTarget(out owner); - if (_getterExtra != null && !lostHandle) - foreach (WeakReference 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 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 _propertyChangedEvent = - new MtObservableEvent(); - - /// - 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)); - } - } -} diff --git a/Torch/Managers/Profiler/ProfilerManager.cs b/Torch/Managers/Profiler/ProfilerManager.cs deleted file mode 100644 index f2ad1d3..0000000 --- a/Torch/Managers/Profiler/ProfilerManager.cs +++ /dev/null @@ -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) - { - } - - /// - /// Profile grid related updates. - /// - public bool ProfileGridsUpdate - { - get => ProfilerData.ProfileGridsUpdate; - set => ProfilerData.ProfileGridsUpdate = value; - } - - /// - /// Profile block updates. Requires - /// - public bool ProfileBlocksUpdate - { - get => ProfilerData.ProfileBlocksUpdate; - set => ProfilerData.ProfileBlocksUpdate = value; - } - - /// - /// Profile entity component updates. - /// - public bool ProfileEntityComponentsUpdate - { - get => ProfilerData.ProfileEntityComponentsUpdate; - set => ProfilerData.ProfileEntityComponentsUpdate = value; - } - - /// - /// Profile grid system updates. Requires - /// - public bool ProfileGridSystemUpdates - { - get => ProfilerData.ProfileGridSystemUpdates; - set => ProfilerData.ProfileGridSystemUpdates = value; - } - - /// - /// Profile session component updates. - /// - public bool ProfileSessionComponentsUpdate - { - get => ProfilerData.ProfileSessionComponentsUpdate; - set => ProfilerData.ProfileSessionComponentsUpdate = value; - } - - /// - /// Gets the profiler information associated with the given entity. - /// - /// Entity to get information for - /// View model to reuse, or null to create a new one - /// Information - public ProfilerEntryViewModel EntityData(IMyEntity entity, ProfilerEntryViewModel cache = null) - { - cache = ProfilerData.BindView(cache); - cache.SetTarget(entity); - return cache; - } - - - /// - /// Gets the profiler information associated with the given cube grid system. - /// - /// Cube grid to query - /// Cube grid system to query - /// View model to reuse, or null to create a new one - /// Information - public ProfilerEntryViewModel GridSystemData(MyCubeGrid grid, object cubeGridSystem, ProfilerEntryViewModel cache = null) - { - cache = ProfilerData.BindView(cache); - cache.SetTarget(grid, cubeGridSystem); - return cache; - } - - - /// - /// Gets the profiler information associated with the given entity component - /// - /// Component to get information for - /// View model to reuse, or null to create a new one - /// Information - public ProfilerEntryViewModel EntityComponentData(MyEntityComponentBase component, ProfilerEntryViewModel cache = null) - { - cache = ProfilerData.BindView(cache); - cache.SetTarget(component); - return cache; - } - - /// - /// Gets the profiler information associated with all entities - /// - /// View model to reuse, or null to create a new one - /// View model - public ProfilerEntryViewModel EntitiesData(ProfilerEntryViewModel cache = null) - { - cache = ProfilerData.BindView(cache); - cache.SetTarget(ProfilerFixedEntry.Entities); - return cache; - } - - /// - /// Gets the profiler information associated with the session - /// - /// View model to reuse, or null to create a new one - /// View model - public ProfilerEntryViewModel SessionData(ProfilerEntryViewModel cache = null) - { - cache = ProfilerData.BindView(cache); - cache.SetTarget(ProfilerFixedEntry.Session); - return cache; - } - } -} diff --git a/Torch/Managers/Profiler/ProfilerObjectIdentifier.cs b/Torch/Managers/Profiler/ProfilerObjectIdentifier.cs deleted file mode 100644 index 1172230..0000000 --- a/Torch/Managers/Profiler/ProfilerObjectIdentifier.cs +++ /dev/null @@ -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 - { - /// - /// Identifies the given object in a human readable name when profiling - /// - /// object to ID - /// ID - 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"; - } - } -} diff --git a/Torch/Managers/Profiler/ProfilerPatch.cs b/Torch/Managers/Profiler/ProfilerPatch.cs deleted file mode 100644 index d26f555..0000000 --- a/Torch/Managers/Profiler/ProfilerPatch.cs +++ /dev/null @@ -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(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 TranspilerForUpdate(IEnumerable insn, Func __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(); - var tmpArgument = new Dictionary>(); - - var foundAny = false; - foreach (MsilInstruction i in insn) - { - if (profilerCall != null && (i.OpCode == OpCodes.Call || i.OpCode == OpCodes.Callvirt) && - ShouldProfileMethodCall((i.Operand as MsilOperandInline)?.Value)) - { - MethodBase target = ((MsilOperandInline)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 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 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)?.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)?.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 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 - } -} diff --git a/Torch/Managers/Profiler/SlimProfilerEntry.cs b/Torch/Managers/Profiler/SlimProfilerEntry.cs deleted file mode 100644 index 7c3da5e..0000000 --- a/Torch/Managers/Profiler/SlimProfilerEntry.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/Torch/Persistent.cs b/Torch/Persistent.cs index 3beff31..670d2ba 100644 --- a/Torch/Persistent.cs +++ b/Torch/Persistent.cs @@ -44,7 +44,7 @@ namespace Torch path = Path; var ser = new XmlSerializer(typeof(T)); - using (var f = File.Create(path)) + using (var f = File.CreateText(path)) { ser.Serialize(f, Data); } @@ -57,7 +57,7 @@ namespace Torch if (File.Exists(path)) { var ser = new XmlSerializer(typeof(T)); - using (var f = File.OpenRead(path)) + using (var f = File.OpenText(path)) { config.Data = (T)ser.Deserialize(f); } diff --git a/Torch/Plugins/PluginManager.cs b/Torch/Plugins/PluginManager.cs index 848a2da..f200bf6 100644 --- a/Torch/Plugins/PluginManager.cs +++ b/Torch/Plugins/PluginManager.cs @@ -17,6 +17,7 @@ using Torch.API.Plugins; using Torch.API.Session; using Torch.Collections; using Torch.Commands; +using Torch.Utils; namespace Torch.Managers { @@ -235,11 +236,27 @@ namespace Torch.Managers if (!file.Contains(".dll", StringComparison.CurrentCultureIgnoreCase)) continue; + if (false) { + var asm = Assembly.LoadFrom(file); + assemblies.Add(asm); + TorchBase.RegisterAuxAssembly(asm); + continue; + } + using (var stream = File.OpenRead(file)) { - var data = new byte[stream.Length]; - stream.Read(data, 0, data.Length); + var data = stream.ReadToEnd(); +#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); +#endif assemblies.Add(asm); TorchBase.RegisterAuxAssembly(asm); } @@ -268,8 +285,7 @@ namespace Torch.Managers using (var stream = entry.Open()) { - var data = new byte[entry.Length]; - stream.Read(data, 0, data.Length); + var data = stream.ReadToEnd(); Assembly asm = Assembly.Load(data); TorchBase.RegisterAuxAssembly(asm); } diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index acc4713..aa6b929 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -193,13 +193,6 @@ - - - - - - - @@ -226,6 +219,7 @@ + diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 2989476..1ccced8 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -27,7 +27,6 @@ using Torch.Event; using Torch.Managers; using Torch.Managers.ChatManager; using Torch.Managers.PatchManager; -using Torch.Managers.Profiler; using Torch.Patches; using Torch.Utils; using Torch.Session; @@ -138,7 +137,6 @@ namespace Torch Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new UpdateManager(this)); Managers.AddManager(new EventManager(this)); - Managers.AddManager(new ProfilerManager(this)); Managers.AddManager(Plugins); TorchAPI.Instance = this; } diff --git a/Torch/Utils/MiscExtensions.cs b/Torch/Utils/MiscExtensions.cs new file mode 100644 index 0000000..4198b03 --- /dev/null +++ b/Torch/Utils/MiscExtensions.cs @@ -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> _streamBuffer = new ThreadLocal>(() => new WeakReference(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; + } + } +}