From e9b432288ede50a43b6ca8874b34003883882cd4 Mon Sep 17 00:00:00 2001 From: John Gross Date: Sun, 16 Jul 2017 10:14:04 -0700 Subject: [PATCH] Optimize UI, add easily accessible restart code, fix bug in network manager RaiseEvent --- Torch.API/ITorchBase.cs | 9 +- Torch.API/Managers/IPluginManager.cs | 4 +- Torch.Client/Properties/AssemblyInfo.cs | 4 +- Torch.Server/Program.cs | 5 +- Torch.Server/Properties/AssemblyInfo.cs | 4 +- Torch.Server/TorchConfig.cs | 4 +- Torch.Server/TorchServer.cs | 23 ++- .../ViewModels/ConfigDedicatedViewModel.cs | 2 +- .../Entities/Blocks/BlockViewModel.cs | 16 +- .../Entities/Blocks/PropertyViewModel.cs | 16 +- .../ViewModels/Entities/EntityViewModel.cs | 1 + .../ViewModels/Entities/GridViewModel.cs | 31 +-- .../ViewModels/Entities/VoxelMapViewModel.cs | 2 +- .../ViewModels/EntityTreeViewModel.cs | 33 ++-- .../ViewModels/PluginManagerViewModel.cs | 6 +- .../ViewModels/SessionSettingsViewModel.cs | 2 +- Torch.Server/Views/ChatControl.xaml | 1 + Torch.Server/Views/ChatControl.xaml.cs | 16 +- Torch.Server/Views/ConfigControl.xaml | 4 +- Torch.Server/Views/ConfigControl.xaml.cs | 1 - Torch.Server/Views/EntitiesControl.xaml | 3 - Torch.Server/Views/EntitiesControl.xaml.cs | 4 +- Torch.Server/Views/TorchUI.xaml.cs | 1 + Torch/Collections/MTObservableCollection.cs | 6 +- Torch/Collections/ObservableList.cs | 186 ++++++++++++++++++ Torch/CommandLine.cs | 5 +- Torch/Commands/CommandManager.cs | 2 +- Torch/Commands/TorchCommands.cs | 49 ++++- Torch/DispatcherExtensions.cs | 17 ++ Torch/Managers/MultiplayerManager.cs | 25 ++- .../Managers/NetworkManager/NetworkManager.cs | 2 +- Torch/Managers/PluginManager.cs | 4 +- Torch/SaveGameStatus.cs | 15 ++ Torch/Torch.csproj | 2 + Torch/TorchBase.cs | 46 ++--- 35 files changed, 441 insertions(+), 110 deletions(-) create mode 100644 Torch/Collections/ObservableList.cs create mode 100644 Torch/DispatcherExtensions.cs diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs index a43aa89..905b85a 100644 --- a/Torch.API/ITorchBase.cs +++ b/Torch.API/ITorchBase.cs @@ -74,12 +74,17 @@ namespace Torch.API /// Stop the Torch instance. /// void Stop(); - + + /// + /// Restart the Torch instance. + /// + void Restart(); + /// /// Initializes a save of the game. /// /// Id of the player who initiated the save. - void Save(long callerId); + Task Save(long callerId); /// /// Initialize the Torch instance. diff --git a/Torch.API/Managers/IPluginManager.cs b/Torch.API/Managers/IPluginManager.cs index a1ad100..2a0d729 100644 --- a/Torch.API/Managers/IPluginManager.cs +++ b/Torch.API/Managers/IPluginManager.cs @@ -14,12 +14,12 @@ namespace Torch.API.Managers /// /// Fired when plugins are loaded. /// - event Action> PluginsLoaded; + event Action> PluginsLoaded; /// /// Collection of loaded plugins. /// - ObservableCollection Plugins { get; } + IList Plugins { get; } /// /// Updates all loaded plugins. diff --git a/Torch.Client/Properties/AssemblyInfo.cs b/Torch.Client/Properties/AssemblyInfo.cs index 0b06134..43cfce9 100644 --- a/Torch.Client/Properties/AssemblyInfo.cs +++ b/Torch.Client/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("1.0.186.642")] -[assembly: AssemblyFileVersion("1.0.186.642")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.195.514")] +[assembly: AssemblyFileVersion("1.0.195.514")] \ No newline at end of file diff --git a/Torch.Server/Program.cs b/Torch.Server/Program.cs index ecf9a3f..17fb1e0 100644 --- a/Torch.Server/Program.cs +++ b/Torch.Server/Program.cs @@ -100,6 +100,8 @@ namespace Torch.Server var waitProc = Process.GetProcessById(pid); _log.Warn($"Waiting for process {pid} to exit."); waitProc.WaitForExit(); + _log.Info("Continuing in 5 seconds."); + Thread.Sleep(5000); } catch { @@ -245,8 +247,8 @@ quit"; }*/ _server = new TorchServer(config); - _server.Init(); + _server.Init(); if (config.NoGui || config.Autostart) { new Thread(() => _server.Start()).Start(); @@ -255,7 +257,6 @@ quit"; if (!config.NoGui) { var ui = new TorchUI((TorchServer)_server); - ui.LoadConfig(config); ui.ShowDialog(); } } diff --git a/Torch.Server/Properties/AssemblyInfo.cs b/Torch.Server/Properties/AssemblyInfo.cs index 0b06134..43cfce9 100644 --- a/Torch.Server/Properties/AssemblyInfo.cs +++ b/Torch.Server/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("1.0.186.642")] -[assembly: AssemblyFileVersion("1.0.186.642")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.195.514")] +[assembly: AssemblyFileVersion("1.0.195.514")] \ No newline at end of file diff --git a/Torch.Server/TorchConfig.cs b/Torch.Server/TorchConfig.cs index 16d37a4..e3268d4 100644 --- a/Torch.Server/TorchConfig.cs +++ b/Torch.Server/TorchConfig.cs @@ -24,7 +24,7 @@ namespace Torch.Server public bool Update { get; set; } /// - [XmlIgnore, Arg("autostart", "Start the server immediately.")] + [Arg("autostart", "Start the server immediately.")] public bool Autostart { get; set; } /// @@ -32,7 +32,7 @@ namespace Torch.Server public bool RestartOnCrash { get; set; } /// - [XmlIgnore, Arg("nogui", "Do not show the Torch UI.")] + [Arg("nogui", "Do not show the Torch UI.")] public bool NoGui { get; set; } /// diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index 813d413..3cd8298 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -10,6 +10,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Security.Principal; using System.Threading; +using System.Threading.Tasks; using Microsoft.Xml.Serialization.GeneratedAssembly; using Sandbox.Engine.Analytics; using Sandbox.Game.Multiplayer; @@ -20,6 +21,7 @@ using Torch.Managers; using VRage.Dedicated; using VRage.FileSystem; using VRage.Game; +using VRage.Game.ModAPI; using VRage.Game.ObjectBuilder; using VRage.Game.SessionComponents; using VRage.Library; @@ -49,6 +51,7 @@ namespace Torch.Server private float _simRatio; private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false); private Timer _watchdog; + private Stopwatch _uptime; public TorchServer(TorchConfig config = null) { @@ -133,6 +136,7 @@ namespace Torch.Server if (State != ServerState.Stopped) return; + _uptime = Stopwatch.StartNew(); IsRunning = true; GameThread = Thread.CurrentThread; Config.Save(); @@ -166,7 +170,8 @@ namespace Torch.Server { base.Update(); SimulationRatio = Sync.ServerSimulationRatio; - ElapsedPlayTime = MySession.Static?.ElapsedPlayTime ?? default(TimeSpan); + var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds)); + ElapsedPlayTime = elapsed; if (_watchdog == null && Instance.Config.TickTimeout > 0) { @@ -215,15 +220,21 @@ namespace Torch.Server IsRunning = false; } - public void Restart() + /// + /// Restart the program. DOES NOT SAVE! + /// + public override void Restart() { - + var exe = Assembly.GetExecutingAssembly().Location; + ((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString(); + Process.Start(exe, Config.ToString()); + Environment.Exit(0); } /// - public override void Save(long callerId) + public override Task Save(long callerId) { - base.SaveGameAsync((statusCode) => SaveCompleted(statusCode, callerId)); + return SaveGameAsync(statusCode => SaveCompleted(statusCode, callerId)); } /// @@ -231,7 +242,7 @@ namespace Torch.Server /// /// Return code of the save operation /// Caller of the save operation - private void SaveCompleted(SaveGameStatus statusCode, long callerId) + private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0) { switch (statusCode) { diff --git a/Torch.Server/ViewModels/ConfigDedicatedViewModel.cs b/Torch.Server/ViewModels/ConfigDedicatedViewModel.cs index e62862d..675089a 100644 --- a/Torch.Server/ViewModels/ConfigDedicatedViewModel.cs +++ b/Torch.Server/ViewModels/ConfigDedicatedViewModel.cs @@ -56,7 +56,7 @@ namespace Torch.Server.ViewModels public SessionSettingsViewModel SessionSettings { get; } - public ObservableCollection WorldPaths { get; } = new ObservableCollection(); + public ObservableList WorldPaths { get; } = new ObservableList(); private string _administrators; public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } } private string _banned; diff --git a/Torch.Server/ViewModels/Entities/Blocks/BlockViewModel.cs b/Torch.Server/ViewModels/Entities/Blocks/BlockViewModel.cs index 56aa4ed..e3cbe76 100644 --- a/Torch.Server/ViewModels/Entities/Blocks/BlockViewModel.cs +++ b/Torch.Server/ViewModels/Entities/Blocks/BlockViewModel.cs @@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Blocks public class BlockViewModel : EntityViewModel { public IMyTerminalBlock Block { get; } - public MTObservableCollection Properties { get; } = new MTObservableCollection(); + public ObservableList Properties { get; } = new ObservableList(); public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}"; @@ -24,8 +24,11 @@ namespace Torch.Server.ViewModels.Blocks get => Block?.CustomName ?? "null"; set { - TorchBase.Instance.InvokeBlocking(() => Block.CustomName = value); - OnPropertyChanged(); + TorchBase.Instance.Invoke(() => + { + Block.CustomName = value; + OnPropertyChanged(); + }); } } @@ -37,8 +40,11 @@ namespace Torch.Server.ViewModels.Blocks get => ((MySlimBlock)Block.SlimBlock).BuiltBy; set { - TorchBase.Instance.InvokeBlocking(() => ((MySlimBlock)Block.SlimBlock).TransferAuthorship(value)); - OnPropertyChanged(); + TorchBase.Instance.Invoke(() => + { + ((MySlimBlock)Block.SlimBlock).TransferAuthorship(value); + OnPropertyChanged(); + }); } } diff --git a/Torch.Server/ViewModels/Entities/Blocks/PropertyViewModel.cs b/Torch.Server/ViewModels/Entities/Blocks/PropertyViewModel.cs index 95f709d..70eba37 100644 --- a/Torch.Server/ViewModels/Entities/Blocks/PropertyViewModel.cs +++ b/Torch.Server/ViewModels/Entities/Blocks/PropertyViewModel.cs @@ -16,17 +16,15 @@ namespace Torch.Server.ViewModels.Blocks public T Value { - get - { - var val = default(T); - TorchBase.Instance.InvokeBlocking(() => val = _prop.GetValue(Block.Block)); - return val; - } + get => _prop.GetValue(Block.Block); set { - TorchBase.Instance.InvokeBlocking(() => _prop.SetValue(Block.Block, value)); - OnPropertyChanged(); - Block.RefreshModel(); + TorchBase.Instance.Invoke(() => + { + _prop.SetValue(Block.Block, value); + OnPropertyChanged(); + Block.RefreshModel(); + }); } } diff --git a/Torch.Server/ViewModels/Entities/EntityViewModel.cs b/Torch.Server/ViewModels/Entities/EntityViewModel.cs index 54e724b..80cae76 100644 --- a/Torch.Server/ViewModels/Entities/EntityViewModel.cs +++ b/Torch.Server/ViewModels/Entities/EntityViewModel.cs @@ -40,6 +40,7 @@ namespace Torch.Server.ViewModels.Entities public EntityViewModel(IMyEntity entity, EntityTreeViewModel tree) { Entity = entity; + Tree = tree; } public EntityViewModel() diff --git a/Torch.Server/ViewModels/Entities/GridViewModel.cs b/Torch.Server/ViewModels/Entities/GridViewModel.cs index 3dd5363..fb55233 100644 --- a/Torch.Server/ViewModels/Entities/GridViewModel.cs +++ b/Torch.Server/ViewModels/Entities/GridViewModel.cs @@ -1,6 +1,9 @@ -using System.Linq; +using System; +using System.Linq; using NLog; using Sandbox.Game.Entities; +using Sandbox.Game.Multiplayer; +using Sandbox.Game.World; using Sandbox.ModAPI; using Torch.Server.ViewModels.Blocks; @@ -9,17 +12,16 @@ namespace Torch.Server.ViewModels.Entities public class GridViewModel : EntityViewModel, ILazyLoad { private MyCubeGrid Grid => (MyCubeGrid)Entity; - public MTObservableCollection Blocks { get; } = new MTObservableCollection(); - private static readonly Logger Log = LogManager.GetLogger(nameof(GridViewModel)); + public ObservableList Blocks { get; } = new ObservableList(); /// - public string DescriptiveName => $"{Name} ({Grid.BlocksCount} blocks)"; + public string DescriptiveName { get; } public GridViewModel() { } public GridViewModel(MyCubeGrid grid, EntityTreeViewModel tree) : base(grid, tree) { - Log.Debug($"Creating model {Grid.DisplayName}"); + DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)"; Blocks.Add(new BlockViewModel(null, Tree)); } @@ -28,7 +30,6 @@ namespace Torch.Server.ViewModels.Entities if (obj.FatBlock != null) Blocks.RemoveWhere(b => b.Block.EntityId == obj.FatBlock?.EntityId); - Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName); OnPropertyChanged(nameof(Name)); } @@ -36,9 +37,8 @@ namespace Torch.Server.ViewModels.Entities { var block = obj.FatBlock as IMyTerminalBlock; if (block != null) - Blocks.Add(new BlockViewModel(block, Tree)); + Blocks.Insert(new BlockViewModel(block, Tree), b => b.Name); - Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName); OnPropertyChanged(nameof(Name)); } @@ -48,20 +48,23 @@ namespace Torch.Server.ViewModels.Entities if (_load) return; - Log.Debug($"Loading model {Grid.DisplayName}"); _load = true; Blocks.Clear(); - TorchBase.Instance.InvokeBlocking(() => + TorchBase.Instance.Invoke(() => { foreach (var block in Grid.GetFatBlocks().Where(b => b is IMyTerminalBlock)) { Blocks.Add(new BlockViewModel((IMyTerminalBlock)block, Tree)); } - }); - Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName); - Grid.OnBlockAdded += Grid_OnBlockAdded; - Grid.OnBlockRemoved += Grid_OnBlockRemoved; + Grid.OnBlockAdded += Grid_OnBlockAdded; + Grid.OnBlockRemoved += Grid_OnBlockRemoved; + + Tree.ControlDispatcher.BeginInvoke(() => + { + Blocks.Sort(b => b.Block.CustomName); + }); + }); } } } diff --git a/Torch.Server/ViewModels/Entities/VoxelMapViewModel.cs b/Torch.Server/ViewModels/Entities/VoxelMapViewModel.cs index e19179a..36a6c1c 100644 --- a/Torch.Server/ViewModels/Entities/VoxelMapViewModel.cs +++ b/Torch.Server/ViewModels/Entities/VoxelMapViewModel.cs @@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Entities public override bool CanStop => false; - public MTObservableCollection AttachedGrids { get; } = new MTObservableCollection(); + public ObservableList AttachedGrids { get; } = new ObservableList(); public async Task UpdateAttachedGrids() { diff --git a/Torch.Server/ViewModels/EntityTreeViewModel.cs b/Torch.Server/ViewModels/EntityTreeViewModel.cs index 37412e1..0bfd279 100644 --- a/Torch.Server/ViewModels/EntityTreeViewModel.cs +++ b/Torch.Server/ViewModels/EntityTreeViewModel.cs @@ -3,22 +3,28 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Controls; using Sandbox.Game.Entities; using Sandbox.Game.Entities.Character; using Torch.Server.ViewModels.Entities; using VRage.Game.ModAPI; using VRage.ModAPI; +using System.Windows.Threading; +using NLog; namespace Torch.Server.ViewModels { public class EntityTreeViewModel : ViewModel { - public MTObservableCollection Grids { get; set; } = new MTObservableCollection(); - public MTObservableCollection Characters { get; set; } = new MTObservableCollection(); - public MTObservableCollection FloatingObjects { get; set; } = new MTObservableCollection(); - public MTObservableCollection VoxelMaps { get; set; } = new MTObservableCollection(); + //TODO: these should be sorted sets for speed + public ObservableList Grids { get; set; } = new ObservableList(); + public ObservableList Characters { get; set; } = new ObservableList(); + public ObservableList FloatingObjects { get; set; } = new ObservableList(); + public ObservableList VoxelMaps { get; set; } = new ObservableList(); + public Dispatcher ControlDispatcher => _control.Dispatcher; private EntityViewModel _currentEntity; + private UserControl _control; public EntityViewModel CurrentEntity { @@ -26,7 +32,12 @@ namespace Torch.Server.ViewModels set { _currentEntity = value; OnPropertyChanged(); } } - public EntityTreeViewModel() + public EntityTreeViewModel(UserControl control) + { + _control = control; + } + + public void Init() { MyEntities.OnEntityAdd += MyEntities_OnEntityAdd; MyEntities.OnEntityRemove += MyEntities_OnEntityRemove; @@ -56,20 +67,16 @@ namespace Torch.Server.ViewModels switch (obj) { case MyCubeGrid grid: - if (Grids.All(g => g.Entity.EntityId != obj.EntityId)) - Grids.Add(new GridViewModel(grid, this)); + Grids.Insert(new GridViewModel(grid, this), g => g.Name); break; case MyCharacter character: - if (Characters.All(g => g.Entity.EntityId != obj.EntityId)) - Characters.Add(new CharacterViewModel(character, this)); + Characters.Insert(new CharacterViewModel(character, this), c => c.Name); break; case MyFloatingObject floating: - if (FloatingObjects.All(g => g.Entity.EntityId != obj.EntityId)) - FloatingObjects.Add(new FloatingObjectViewModel(floating, this)); + FloatingObjects.Insert(new FloatingObjectViewModel(floating, this), f => f.Name); break; case MyVoxelBase voxel: - if (VoxelMaps.All(g => g.Entity.EntityId != obj.EntityId)) - VoxelMaps.Add(new VoxelMapViewModel(voxel, this)); + VoxelMaps.Insert(new VoxelMapViewModel(voxel, this), v => v.Name); break; } } diff --git a/Torch.Server/ViewModels/PluginManagerViewModel.cs b/Torch.Server/ViewModels/PluginManagerViewModel.cs index d6e7aa1..ed43547 100644 --- a/Torch.Server/ViewModels/PluginManagerViewModel.cs +++ b/Torch.Server/ViewModels/PluginManagerViewModel.cs @@ -11,7 +11,7 @@ namespace Torch.Server.ViewModels { public class PluginManagerViewModel : ViewModel { - public MTObservableCollection Plugins { get; } = new MTObservableCollection(); + public ObservableList Plugins { get; } = new ObservableList(); private PluginViewModel _selectedPlugin; public PluginViewModel SelectedPlugin @@ -24,10 +24,12 @@ namespace Torch.Server.ViewModels public PluginManagerViewModel(IPluginManager pluginManager) { + foreach (var plugin in pluginManager) + Plugins.Add(new PluginViewModel(plugin)); pluginManager.PluginsLoaded += PluginManager_PluginsLoaded; } - private void PluginManager_PluginsLoaded(List obj) + private void PluginManager_PluginsLoaded(IList obj) { Plugins.Clear(); foreach (var plugin in obj) diff --git a/Torch.Server/ViewModels/SessionSettingsViewModel.cs b/Torch.Server/ViewModels/SessionSettingsViewModel.cs index 9dc583b..4dc3875 100644 --- a/Torch.Server/ViewModels/SessionSettingsViewModel.cs +++ b/Torch.Server/ViewModels/SessionSettingsViewModel.cs @@ -35,7 +35,7 @@ namespace Torch.Server.ViewModels BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value)); } - public MTObservableCollection BlockLimits { get; } = new MTObservableCollection(); + public ObservableList BlockLimits { get; } = new ObservableList(); #region Multipliers diff --git a/Torch.Server/Views/ChatControl.xaml b/Torch.Server/Views/ChatControl.xaml index a9186c4..5e1b453 100644 --- a/Torch.Server/Views/ChatControl.xaml +++ b/Torch.Server/Views/ChatControl.xaml @@ -11,6 +11,7 @@ + diff --git a/Torch.Server/Views/ChatControl.xaml.cs b/Torch.Server/Views/ChatControl.xaml.cs index 52d4562..832f329 100644 --- a/Torch.Server/Views/ChatControl.xaml.cs +++ b/Torch.Server/Views/ChatControl.xaml.cs @@ -41,8 +41,10 @@ namespace Torch.Server { _server = (TorchBase)server; _multiplayer = (MultiplayerManager)server.Multiplayer; + ChatItems.Items.Clear(); DataContext = _multiplayer; - _multiplayer.ChatHistory.CollectionChanged += ChatHistory_CollectionChanged; + if (_multiplayer.ChatHistory is INotifyCollectionChanged ncc) + ncc.CollectionChanged += ChatHistory_CollectionChanged; } private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -74,22 +76,26 @@ namespace Torch.Server return; var commands = _server.Commands; - string response = null; if (commands.IsCommand(text)) { _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text)); - _server.InvokeBlocking(() => + _server.Invoke(() => { - response = commands.HandleCommandFromServer(text); + var response = commands.HandleCommandFromServer(text); + Dispatcher.BeginInvoke(() => OnMessageEntered_Callback(response)); }); } else { _server.Multiplayer.SendMessage(text); } + Message.Text = ""; + } + + private void OnMessageEntered_Callback(string response) + { if (!string.IsNullOrEmpty(response)) _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response)); - Message.Text = ""; } } } diff --git a/Torch.Server/Views/ConfigControl.xaml b/Torch.Server/Views/ConfigControl.xaml index f399687..2071c40 100644 --- a/Torch.Server/Views/ConfigControl.xaml +++ b/Torch.Server/Views/ConfigControl.xaml @@ -80,7 +80,7 @@ public partial class EntitiesControl : UserControl { - public EntityTreeViewModel Entities { get; set; } = new EntityTreeViewModel(); + public EntityTreeViewModel Entities { get; set; } public EntitiesControl() { InitializeComponent(); + Entities = new EntityTreeViewModel(this); DataContext = Entities; + Entities.Init(); } private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) diff --git a/Torch.Server/Views/TorchUI.xaml.cs b/Torch.Server/Views/TorchUI.xaml.cs index 23d1dfe..071488d 100644 --- a/Torch.Server/Views/TorchUI.xaml.cs +++ b/Torch.Server/Views/TorchUI.xaml.cs @@ -47,6 +47,7 @@ namespace Torch.Server Chat.BindServer(server); PlayerList.BindServer(server); Plugins.BindServer(server); + LoadConfig((TorchConfig)server.Config); } public void LoadConfig(TorchConfig config) diff --git a/Torch/Collections/MTObservableCollection.cs b/Torch/Collections/MTObservableCollection.cs index 89e3f86..fa513aa 100644 --- a/Torch/Collections/MTObservableCollection.cs +++ b/Torch/Collections/MTObservableCollection.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -10,15 +9,18 @@ using System.Windows.Threading; namespace Torch { + [Obsolete("Use ObservableList.")] public class MTObservableCollection : ObservableCollection { public override event NotifyCollectionChangedEventHandler CollectionChanged; + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged; if (collectionChanged != null) - foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList()) + foreach (var del in collectionChanged.GetInvocationList()) { + var nh = (NotifyCollectionChangedEventHandler)del; var dispObj = nh.Target as DispatcherObject; var dispatcher = dispObj?.Dispatcher; diff --git a/Torch/Collections/ObservableList.cs b/Torch/Collections/ObservableList.cs new file mode 100644 index 0000000..a4350e8 --- /dev/null +++ b/Torch/Collections/ObservableList.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows.Threading; + +namespace Torch +{ + /// + /// An observable version of . + /// + public class ObservableList : IList, INotifyCollectionChanged, INotifyPropertyChanged + { + private List _internalList = new List(); + + /// + public event NotifyCollectionChangedEventHandler CollectionChanged; + + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + public void Clear() + { + _internalList.Clear(); + OnPropertyChanged(nameof(Count)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + /// + public bool Contains(T item) + { + return _internalList.Contains(item); + } + + /// + public void CopyTo(T[] array, int arrayIndex) + { + _internalList.CopyTo(array, arrayIndex); + } + + /// + public bool Remove(T item) + { + var oldIndex = _internalList.IndexOf(item); + if (!_internalList.Remove(item)) + return false; + + OnPropertyChanged(nameof(Count)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, oldIndex)); + return true; + } + + /// + public int Count => _internalList.Count; + + /// + public bool IsReadOnly => false; + + /// + public void Add(T item) + { + _internalList.Add(item); + OnPropertyChanged(nameof(Count)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, Count - 1)); + } + + /// + public int IndexOf(T item) => _internalList.IndexOf(item); + + /// + public void Insert(int index, T item) + { + _internalList.Insert(index, item); + OnPropertyChanged(nameof(Count)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + } + + /// + /// Inserts an item in order based on the provided selector and comparer. This will only work properly on a pre-sorted list. + /// + public void Insert(T item, Func selector, IComparer comparer = null) + { + comparer = comparer ?? Comparer.Default; + var key1 = selector(item); + for (var i = 0; i < _internalList.Count; i++) + { + var key2 = selector(_internalList[i]); + if (comparer.Compare(key1, key2) < 1) + { + Insert(i, item); + return; + } + } + + Add(item); + } + + /// + public void RemoveAt(int index) + { + var old = this[index]; + _internalList.RemoveAt(index); + OnPropertyChanged(nameof(Count)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, index)); + } + + public T this[int index] + { + get => _internalList[index]; + set + { + var old = _internalList[index]; + if (old.Equals(value)) + return; + + _internalList[index] = value; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, old, index)); + } + } + + /// + /// Sorts the list using the given selector and comparer./> + /// + public void Sort(Func selector, IComparer comparer = null) + { + comparer = comparer ?? Comparer.Default; + var sortedItems = _internalList.OrderBy(selector, comparer).ToList(); + + _internalList = sortedItems; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + /// + /// Removes all items that satisfy the given condition. + /// + public void RemoveWhere(Func condition) + { + for (var i = Count - 1; i > 0; i--) + { + if (condition?.Invoke(this[i]) ?? false) + RemoveAt(i); + } + } + + protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + var collectionChanged = CollectionChanged; + if (collectionChanged != null) + foreach (var del in collectionChanged.GetInvocationList()) + { + var nh = (NotifyCollectionChangedEventHandler)del; + var dispObj = nh.Target as DispatcherObject; + + var dispatcher = dispObj?.Dispatcher; + if (dispatcher != null && !dispatcher.CheckAccess()) + { + dispatcher.BeginInvoke(() => nh.Invoke(this, e), DispatcherPriority.DataBind); + continue; + } + + nh.Invoke(this, e); + } + } + + protected void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + public IEnumerator GetEnumerator() + { + return _internalList.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_internalList).GetEnumerator(); + } + } +} diff --git a/Torch/CommandLine.cs b/Torch/CommandLine.cs index 3085ae2..4ca10a4 100644 --- a/Torch/CommandLine.cs +++ b/Torch/CommandLine.cs @@ -6,7 +6,10 @@ using System.Text; namespace Torch { - public class CommandLine + /// + /// Base class that adds tools for setting type properties through the command line. + /// + public abstract class CommandLine { private readonly string _argPrefix; private readonly Dictionary _args = new Dictionary(); diff --git a/Torch/Commands/CommandManager.cs b/Torch/Commands/CommandManager.cs index 2cf324b..f19fec0 100644 --- a/Torch/Commands/CommandManager.cs +++ b/Torch/Commands/CommandManager.cs @@ -21,7 +21,7 @@ namespace Torch.Commands public CommandTree Commands { get; set; } = new CommandTree(); private Logger _log = LogManager.GetLogger(nameof(CommandManager)); - public CommandManager(ITorchBase torch, char prefix = '/') : base(torch) + public CommandManager(ITorchBase torch, char prefix = '!') : base(torch) { Prefix = prefix; } diff --git a/Torch/Commands/TorchCommands.cs b/Torch/Commands/TorchCommands.cs index 5643ce2..62774ce 100644 --- a/Torch/Commands/TorchCommands.cs +++ b/Torch/Commands/TorchCommands.cs @@ -45,6 +45,42 @@ namespace Torch.Commands } } + [Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")] + public void LongHelp() + { + var commandManager = Context.Torch.GetManager(); + commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node); + + if (node != null) + { + var command = node.Command; + var children = node.Subcommands.Select(x => x.Key); + + var sb = new StringBuilder(); + + if (command != null) + { + sb.AppendLine($"Syntax: {command.SyntaxHelp}"); + sb.Append(command.HelpText); + } + + if (node.Subcommands.Count() != 0) + sb.Append($"\nSubcommands: {string.Join(", ", children)}"); + + Context.Respond(sb.ToString()); + } + else + { + var sb = new StringBuilder("Available commands:\n"); + foreach (var command in commandManager.Commands.WalkTree()) + { + if (command.IsCommand) + sb.AppendLine($"{command.Command.SyntaxHelp}\n {command.Command.HelpText}"); + } + Context.Respond(sb.ToString()); + } + } + [Command("ver", "Shows the running Torch version.")] [Permission(MyPromoteLevel.None)] public void Version() @@ -62,11 +98,22 @@ namespace Torch.Commands } [Command("stop", "Stops the server.")] - public void Stop() + public void Stop(bool save = true) { Context.Respond("Stopping server."); + if (save) + Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait(); Context.Torch.Stop(); } + + [Command("restart", "Restarts the server.")] + public void Restart(bool save = true) + { + Context.Respond("Restarting server."); + if (save) + Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait(); + Context.Torch.Restart(); + } /// /// Initializes a save of the game. diff --git a/Torch/DispatcherExtensions.cs b/Torch/DispatcherExtensions.cs new file mode 100644 index 0000000..7d4ea60 --- /dev/null +++ b/Torch/DispatcherExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Threading; + +namespace Torch +{ + public static class DispatcherExtensions + { + public static DispatcherOperation BeginInvoke(this Dispatcher dispatcher, Action action, DispatcherPriority priority = DispatcherPriority.Normal) + { + return dispatcher.BeginInvoke(priority, action); + } + } +} diff --git a/Torch/Managers/MultiplayerManager.cs b/Torch/Managers/MultiplayerManager.cs index 998836b..f1b0465 100644 --- a/Torch/Managers/MultiplayerManager.cs +++ b/Torch/Managers/MultiplayerManager.cs @@ -16,6 +16,7 @@ using NLog; using Torch; using Sandbox; using Sandbox.Engine.Multiplayer; +using Sandbox.Game.Entities.Character; using Sandbox.Game.Multiplayer; using Sandbox.Game.World; using Sandbox.ModAPI; @@ -43,7 +44,7 @@ namespace Torch.Managers /// public event MessageReceivedDel MessageReceived; - public MTObservableCollection ChatHistory { get; } = new MTObservableCollection(); + public IList ChatHistory { get; } = new ObservableList(); public ObservableDictionary Players { get; } = new ObservableDictionary(); public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer; private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager)); @@ -99,6 +100,17 @@ namespace Torch.Managers return p; } + public ulong GetSteamId(long identityId) + { + foreach (var kv in _onlinePlayers) + { + if (kv.Value.Identity.IdentityId == identityId) + return kv.Key.SteamId; + } + + return 0; + } + /// public string GetSteamUsername(ulong steamId) { @@ -108,17 +120,24 @@ namespace Torch.Managers /// public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red) { - ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", message)); + ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, message)); var commands = Torch.GetManager(); if (commands.IsCommand(message)) { var response = commands.HandleCommandFromServer(message); - ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response)); + ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, response)); } else { var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message }; MyMultiplayerBase.SendScriptedChatMessage(ref msg); + var character = MySession.Static.Players.TryGetIdentity(playerId)?.Character; + var steamId = GetSteamId(playerId); + if (character == null) + return; + + var addToGlobalHistoryMethod = typeof(MyCharacter).GetMethod("OnGlobalMessageSuccess", BindingFlags.Instance | BindingFlags.NonPublic); + Torch.GetManager().RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message); } } diff --git a/Torch/Managers/NetworkManager/NetworkManager.cs b/Torch/Managers/NetworkManager/NetworkManager.cs index 1e86254..a262690 100644 --- a/Torch/Managers/NetworkManager/NetworkManager.cs +++ b/Torch/Managers/NetworkManager/NetworkManager.cs @@ -311,7 +311,7 @@ namespace Torch.Managers var parameters = method.GetParameters(); for (var i = 0; i < parameters.Length; i++) { - if (argTypes[i] != parameters[i].ParameterType) + if (argTypes[i + 1] != parameters[i].ParameterType) throw new TypeLoadException($"Type mismatch on method parameters. Expected {string.Join(", ", parameters.Select(p => p.ParameterType.ToString()))} got {string.Join(", ", argTypes.Select(t => t.ToString()))}"); } diff --git a/Torch/Managers/PluginManager.cs b/Torch/Managers/PluginManager.cs index 6171645..c39a5c0 100644 --- a/Torch/Managers/PluginManager.cs +++ b/Torch/Managers/PluginManager.cs @@ -23,9 +23,9 @@ namespace Torch.Managers private UpdateManager _updateManager; /// - public ObservableCollection Plugins { get; } = new ObservableCollection(); + public IList Plugins { get; } = new ObservableList(); - public event Action> PluginsLoaded; + public event Action> PluginsLoaded; public PluginManager(ITorchBase torchInstance) : base(torchInstance) { diff --git a/Torch/SaveGameStatus.cs b/Torch/SaveGameStatus.cs index 0309212..824476c 100644 --- a/Torch/SaveGameStatus.cs +++ b/Torch/SaveGameStatus.cs @@ -5,9 +5,24 @@ /// public enum SaveGameStatus : byte { + /// + /// The game was saved. + /// Success = 0, + + /// + /// A save operation is already in progress. + /// SaveInProgress = 1, + + /// + /// The game is not in a save-able state. + /// GameNotReady = 2, + + /// + /// The save operation timed out. + /// TimedOut = 3 }; } diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index b61f3c8..b36d79d 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -146,6 +146,8 @@ + + diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 3f461ae..da54215 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -128,7 +128,7 @@ namespace Torch return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId; } - public async Task SaveGameAsync(Action callback) + public Task SaveGameAsync(Action callback) { Log.Info("Saving game"); @@ -142,26 +142,17 @@ namespace Torch } else { - using (var e = new AutoResetEvent(false)) - { - MyAsyncSaving.Start(() => - { - MySector.ResetEyeAdaptation = true; - e.Set(); - }); + var e = new AutoResetEvent(false); + MyAsyncSaving.Start(() => e.Set()); - await Task.Run(() => - { - if (e.WaitOne(60000)) - { - callback?.Invoke(SaveGameStatus.Success); - return; - } - - callback?.Invoke(SaveGameStatus.TimedOut); - }).ConfigureAwait(false); - } + return Task.Run(() => + { + callback?.Invoke(e.WaitOne(5000) ? SaveGameStatus.Success : SaveGameStatus.TimedOut); + e.Dispose(); + }); } + + return Task.CompletedTask; } #region Game Actions @@ -306,19 +297,28 @@ namespace Torch } /// - public virtual void Save(long callerId) + public virtual Task Save(long callerId) { - + return Task.CompletedTask; } - /// public virtual void Start() { } /// - public virtual void Stop() { } + public virtual void Stop() + { + + } + + /// + public virtual void Restart() + { + + } /// public virtual void Dispose()