Add basic, experimental entity sorting

This commit is contained in:
Brant Martin
2019-02-10 16:29:08 -05:00
parent dda7864c1a
commit 1c6eec61af
9 changed files with 333 additions and 34 deletions

View File

@@ -1,8 +1,14 @@
using System.Windows.Controls;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Controls;
using NLog;
using Sandbox.Game.Entities;
using Sandbox.Game.World;
using Torch.API.Managers;
using Torch.Collections;
using Torch.Server.Managers;
using Torch.Utils;
using VRage.Game.Entity;
using VRage.Game.ModAPI;
using VRage.ModAPI;
@@ -14,6 +20,8 @@ namespace Torch.Server.ViewModels.Entities
{
protected EntityTreeViewModel Tree { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IMyEntity _backing;
public IMyEntity Entity
{
@@ -43,6 +51,75 @@ namespace Torch.Server.ViewModels.Entities
}
}
private string _descriptiveName;
public string DescriptiveName
{
get => _descriptiveName ?? (_descriptiveName = GetSortedName(EntityTreeViewModel.SortEnum.Name));
set => _descriptiveName = value;
}
public virtual string GetSortedName(EntityTreeViewModel.SortEnum sort)
{
switch (sort)
{
case EntityTreeViewModel.SortEnum.Name:
return Name;
case EntityTreeViewModel.SortEnum.Size:
return $"{Name} ({Entity.WorldVolume.Radius * 2:N}m)";
case EntityTreeViewModel.SortEnum.Speed:
return $"{Name} ({Entity.Physics?.LinearVelocity.Length() ?? 0:N}m/s)";
case EntityTreeViewModel.SortEnum.BlockCount:
if (Entity is MyCubeGrid grid)
return $"{Name} ({grid.BlocksCount} blocks)";
return Name;
case EntityTreeViewModel.SortEnum.DistFromCenter:
return $"{Name} ({Entity.GetPosition().Length():N}m)";
case EntityTreeViewModel.SortEnum.Owner:
if (Entity is MyCubeGrid g)
return $"{Name} ({g.GetGridOwnerName()})";
return Name;
default:
throw new ArgumentOutOfRangeException(nameof(sort), sort, null);
}
}
public virtual int CompareToSort(EntityViewModel other, EntityTreeViewModel.SortEnum sort)
{
switch (sort)
{
case EntityTreeViewModel.SortEnum.Name:
return string.Compare(Name, other.Name, StringComparison.InvariantCultureIgnoreCase);
case EntityTreeViewModel.SortEnum.Size:
return Entity.WorldVolume.Radius.CompareTo(other.Entity.WorldVolume.Radius);
case EntityTreeViewModel.SortEnum.Speed:
if (Entity.Physics == null)
{
if (other.Entity.Physics == null)
return 0;
return -1;
}
if (other.Entity.Physics == null)
return 1;
return Entity.Physics.LinearVelocity.LengthSquared().CompareTo(other.Entity.Physics.LinearVelocity.LengthSquared());
case EntityTreeViewModel.SortEnum.BlockCount:
{
if (Entity is MyCubeGrid ga && other.Entity is MyCubeGrid gb)
return ga.BlocksCount.CompareTo(gb.BlocksCount);
goto case EntityTreeViewModel.SortEnum.Name;
}
case EntityTreeViewModel.SortEnum.DistFromCenter:
return Entity.GetPosition().LengthSquared().CompareTo(other.Entity.GetPosition().LengthSquared());
case EntityTreeViewModel.SortEnum.Owner:
{
if (Entity is MyCubeGrid ga && other.Entity is MyCubeGrid gb)
return string.Compare(ga.GetGridOwnerName(), gb.GetGridOwnerName(), StringComparison.InvariantCultureIgnoreCase);
goto case EntityTreeViewModel.SortEnum.Name;
}
default:
throw new ArgumentOutOfRangeException(nameof(sort), sort, null);
}
}
public virtual string Position
{
get => Entity?.GetPosition().ToString();
@@ -76,5 +153,20 @@ namespace Torch.Server.ViewModels.Entities
{
}
public class Comparer : IComparer<EntityViewModel>
{
private EntityTreeViewModel.SortEnum _sort;
public Comparer(EntityTreeViewModel.SortEnum sort)
{
_sort = sort;
}
public int Compare(EntityViewModel x, EntityViewModel y)
{
return x.CompareToSort(y, _sort);
}
}
}
}

View File

@@ -51,16 +51,13 @@ namespace Torch.Server.ViewModels.Entities
new MtObservableSortedDictionary<MyCubeBlockDefinition, MtObservableSortedDictionary<long, BlockViewModel>>(
CubeBlockDefinitionComparer.Default);
/// <inheritdoc />
public string DescriptiveName { get; }
public GridViewModel()
{
}
public GridViewModel(MyCubeGrid grid, EntityTreeViewModel tree) : base(grid, tree)
{
DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)";
//DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)";
Blocks.Add(_fillerDefinition, new MtObservableSortedDictionary<long, BlockViewModel>());
}

View File

@@ -12,11 +12,21 @@ using VRage.ModAPI;
using System.Windows.Threading;
using NLog;
using Torch.Collections;
using Torch.Server.Views.Entities;
namespace Torch.Server.ViewModels
{
public class EntityTreeViewModel : ViewModel
{
public enum SortEnum
{
Name,
Size,
Speed,
Owner,
BlockCount,
DistFromCenter,
}
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
//TODO: these should be sorted sets for speed
@@ -26,7 +36,13 @@ namespace Torch.Server.ViewModels
public MtObservableSortedDictionary<long, VoxelMapViewModel> VoxelMaps { get; set; } = new MtObservableSortedDictionary<long, VoxelMapViewModel>();
public Dispatcher ControlDispatcher => _control.Dispatcher;
public SortedView<GridViewModel> SortedGrids { get; }
public SortedView<CharacterViewModel> SortedCharacters { get; }
public SortedView<EntityViewModel> SortedFloatingObjects { get; }
public SortedView<VoxelMapViewModel> SortedVoxelMaps { get; }
private EntityViewModel _currentEntity;
private SortEnum _currentSort;
private UserControl _control;
public EntityViewModel CurrentEntity
@@ -35,6 +51,12 @@ namespace Torch.Server.ViewModels
set { _currentEntity = value; OnPropertyChanged(nameof(CurrentEntity)); }
}
public SortEnum CurrentSort
{
get => _currentSort;
set => SetValue(ref _currentSort, value);
}
// I hate you today WPF
public EntityTreeViewModel() : this(null)
{
@@ -43,6 +65,11 @@ namespace Torch.Server.ViewModels
public EntityTreeViewModel(UserControl control)
{
_control = control;
var comparer = new EntityViewModel.Comparer(_currentSort);
SortedGrids = new SortedView<GridViewModel>(Grids.Values, comparer);
SortedCharacters = new SortedView<CharacterViewModel>(Characters.Values, comparer);
SortedFloatingObjects = new SortedView<EntityViewModel>(FloatingObjects.Values, comparer);
SortedVoxelMaps = new SortedView<VoxelMapViewModel>(VoxelMaps.Values, comparer);
}
public void Init()
@@ -85,16 +112,16 @@ namespace Torch.Server.ViewModels
switch (obj)
{
case MyCubeGrid grid:
Grids.Add(obj.EntityId, new GridViewModel(grid, this));
Grids.Add(grid.EntityId, new GridViewModel(grid, this));
break;
case MyCharacter character:
Characters.Add(obj.EntityId, new CharacterViewModel(character, this));
Characters.Add(character.EntityId, new CharacterViewModel(character, this));
break;
case MyFloatingObject floating:
FloatingObjects.Add(obj.EntityId, new FloatingObjectViewModel(floating, this));
FloatingObjects.Add(floating.EntityId, new FloatingObjectViewModel(floating, this));
break;
case MyVoxelBase voxel:
VoxelMaps.Add(obj.EntityId, new VoxelMapViewModel(voxel, this));
VoxelMaps.Add(voxel.EntityId, new VoxelMapViewModel(voxel, this));
break;
}
}

View File

@@ -23,9 +23,10 @@
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TreeView Grid.Row="0" Margin="3" DockPanel.Dock="Top" SelectedItemChanged="TreeView_OnSelectedItemChanged"
TreeViewItem.Expanded="TreeViewItem_OnExpanded">
TreeViewItem.Expanded="TreeViewItem_OnExpanded" Name="EntityTree">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type entities:GridViewModel}"
ItemsSource="{Binding Path=Blocks}">
@@ -46,46 +47,47 @@
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type entities:VoxelMapViewModel}"
ItemsSource="{Binding AttachedGrids}">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding DescriptiveName}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeViewItem ItemsSource="{Binding Path=Grids.Values}">
<TreeViewItem ItemsSource="{Binding Path=SortedGrids}">
<TreeViewItem.Header>
<TextBlock Text="{Binding Grids.Count, StringFormat=Grids ({0})}" />
<TextBlock Text="{Binding SortedGrids.Count, StringFormat=Grids ({0})}" />
</TreeViewItem.Header>
</TreeViewItem>
<TreeViewItem ItemsSource="{Binding Characters.Values}">
<TreeViewItem ItemsSource="{Binding SortedCharacters}">
<TreeViewItem.Header>
<TextBlock Text="{Binding Characters.Count, StringFormat=Characters ({0})}" />
<TextBlock Text="{Binding SortedCharacters.Count, StringFormat=Characters ({0})}" />
</TreeViewItem.Header>
<TreeViewItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding DescriptiveName}" />
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
<TreeViewItem ItemsSource="{Binding VoxelMaps.Values}">
<TreeViewItem ItemsSource="{Binding SortedVoxelMaps}">
<TreeViewItem.Header>
<TextBlock Text="{Binding VoxelMaps.Count, StringFormat=Voxel Maps ({0})}" />
<TextBlock Text="{Binding SortedVoxelMaps.Count, StringFormat=Voxel Maps ({0})}" />
</TreeViewItem.Header>
<TreeViewItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding DescriptiveName}" />
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
<TreeViewItem ItemsSource="{Binding FloatingObjects.Values}">
<TreeViewItem ItemsSource="{Binding SortedFloatingObjects}">
<TreeViewItem.Header>
<TextBlock Text="{Binding FloatingObjects.Count, StringFormat=Floating Objects ({0})}" />
<TextBlock Text="{Binding SortedFloatingObjects.Count, StringFormat=Floating Objects ({0})}" />
</TreeViewItem.Header>
<TreeViewItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding DescriptiveName}" />
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</TreeView>
<StackPanel Grid.Row="1" DockPanel.Dock="Bottom">
<ComboBox Grid.Row="1" Margin="3" Name="SortCombo" SelectionChanged="SortCombo_SelectionChanged"/>
<StackPanel Grid.Row="2" DockPanel.Dock="Bottom">
<Button Content="Delete" Click="Delete_OnClick" IsEnabled="{Binding CurrentEntity.CanDelete}"
Margin="3" />
<Button Content="Stop" Click="Stop_OnClick" IsEnabled="{Binding CurrentEntity.CanStop}" Margin="3" />

View File

@@ -35,6 +35,7 @@ namespace Torch.Server.Views
Entities = new EntityTreeViewModel(this);
DataContext = Entities;
Entities.Init();
SortCombo.ItemsSource = Enum.GetNames(typeof(EntityTreeViewModel.SortEnum));
}
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
@@ -77,5 +78,30 @@ namespace Torch.Server.Views
if (item.DataContext is ILazyLoad l)
l.Load();
}
private void SortCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var sort = (EntityTreeViewModel.SortEnum)SortCombo.SelectedIndex;
var comparer = new EntityViewModel.Comparer(sort);
Task[] sortTasks = new Task[4];
Entities.CurrentSort = sort;
Entities.SortedCharacters.Sort(comparer);
Entities.SortedFloatingObjects.Sort(comparer);
Entities.SortedGrids.Sort(comparer);
Entities.SortedVoxelMaps.Sort(comparer);
foreach (var i in Entities.SortedCharacters)
i.DescriptiveName = i.GetSortedName(sort);
foreach (var i in Entities.SortedFloatingObjects)
i.DescriptiveName = i.GetSortedName(sort);
foreach (var i in Entities.SortedGrids)
i.DescriptiveName = i.GetSortedName(sort);
foreach (var i in Entities.SortedVoxelMaps)
i.DescriptiveName = i.GetSortedName(sort);
}
}
}

View File

@@ -13,7 +13,7 @@ namespace Torch.Collections
/// Multithread safe, observable list
/// </summary>
/// <typeparam name="T">Value type</typeparam>
public class MtObservableList<T> : MtObservableCollection<IList<T>, T>, IList<T>, IList
public class MtObservableList<T> : MtObservableCollection<List<T>, T>, IList<T>, IList
{
/// <summary>
/// Initializes a new instance of the MtObservableList class that is empty and has the default initial capacity.
@@ -114,16 +114,34 @@ namespace Torch.Collections
using (Lock.WriteUsing())
{
comparer = comparer ?? Comparer<TKey>.Default;
if (Backing is List<T> lst)
lst.Sort(new TransformComparer<T, TKey>(selector, comparer));
else
Backing.Sort(new TransformComparer<T, TKey>(selector, comparer));
}
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move));
}
/// <summary>
/// Sorts the list using the given comparer./>
/// </summary>
public void Sort(IComparer<T> comparer)
{
List<T> sortedItems = Backing.OrderBy(selector, comparer).ToList();
Backing.Clear();
foreach (T v in sortedItems)
Backing.Add(v);
using (DeferredUpdate())
using (Lock.WriteUsing())
{
Backing.Sort(comparer);
}
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move));
}
/// <summary>
/// Searches the entire list for an element using the specified comparer and returns the zero-based index of the element.
/// </summary>
/// <param name="item"></param>
/// <param name="comparer"></param>
/// <returns></returns>
public int BinarySearch(T item, IComparer<T> comparer = null)
{
using(Lock.ReadUsing())
return Backing.BinarySearch(item, comparer ?? Comparer<T>.Default);
}
/// <inheritdoc/>

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Collections
{
public class SortedView<T>: IReadOnlyCollection<T>, INotifyCollectionChanged
{
private readonly MtObservableCollectionBase<T> _backing;
private IComparer<T> _comparer;
private readonly List<T> _store;
public SortedView(MtObservableCollectionBase<T> backing, IComparer<T> comparer)
{
_comparer = comparer;
_backing = backing;
_store = new List<T>(_backing.Count);
_store.AddRange(_backing);
_backing.CollectionChanged += backing_CollectionChanged;
}
private void backing_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
InsertSorted(e.NewItems);
CollectionChanged?.Invoke(this, e);
break;
case NotifyCollectionChangedAction.Remove:
_store.RemoveAll(r => e.OldItems.Contains(r));
CollectionChanged?.Invoke(this, e);
break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
Refresh();
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public IEnumerator<T> GetEnumerator()
{
return _backing.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count => _backing.Count;
private void InsertSorted(IEnumerable items)
{
foreach (var t in items)
InsertSorted((T)t);
}
private int InsertSorted(T item, IComparer<T> comparer = null)
{
if (comparer == null)
comparer = _comparer;
if (_store.Count == 0 || comparer == null)
{
_store.Add(item);
return 0;
}
if(comparer.Compare(_store[_store.Count - 1], item) <= 0)
{
_store.Add(item);
return _store.Count - 1;
}
if(comparer.Compare(_store[0], item) >= 0)
{
_store.Insert(0, item);
return 0;
}
int index = _store.BinarySearch(item);
if (index < 0)
index = ~index;
_store.Insert(index, item);
return index;
}
public void Sort(IComparer<T> comparer = null)
{
if (comparer == null)
comparer = _comparer;
if (comparer == null)
return;
_store.Sort(comparer);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void Refresh()
{
_store.Clear();
_store.AddRange(_backing);
Sort();
}
public void SetComparer(IComparer<T> comparer, bool resort = true)
{
_comparer = comparer;
if(resort)
Sort();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
}

View File

@@ -34,6 +34,8 @@
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.xml</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<!-- I don't know why this needs to exist -->
<ItemGroup> <Reference Include="netstandard" /> </ItemGroup>
<ItemGroup>
<Reference Include="ControlzEx, Version=3.0.2.4, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ControlzEx.3.0.2.4\lib\net45\ControlzEx.dll</HintPath>
@@ -182,6 +184,7 @@
<Compile Include="Collections\MtObservableSortedDictionary.cs" />
<Compile Include="Collections\MtObservableEvent.cs" />
<Compile Include="Collections\MtObservableList.cs" />
<Compile Include="Collections\SortedView.cs" />
<Compile Include="Collections\TransformComparer.cs" />
<Compile Include="Collections\TransformEnumerator.cs" />
<Compile Include="Event\EventShimAttribute.cs" />

View File

@@ -6,7 +6,12 @@ using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Entities;
using Sandbox.Game.World;
using Steamworks;
using VRage.Game.ModAPI;
namespace Torch.Utils
{
@@ -57,5 +62,12 @@ namespace Torch.Utils
// What is endianness anyway?
return new IPAddress(BitConverter.GetBytes(state.m_nRemoteIP).Reverse().ToArray());
}
public static string GetGridOwnerName(this MyCubeGrid grid)
{
if (grid.BigOwners.Count == 0 || grid.BigOwners[0] == 0)
return "nobody";
return MyMultiplayer.Static.GetMemberName(MySession.Static.Players.TryGetSteamId(grid.BigOwners[0]));
}
}
}