Optimize UI, add easily accessible restart code, fix bug in network manager RaiseEvent

This commit is contained in:
John Gross
2017-07-16 10:14:04 -07:00
parent b814d1210b
commit e9b432288e
35 changed files with 441 additions and 110 deletions

View File

@@ -74,12 +74,17 @@ namespace Torch.API
/// Stop the Torch instance. /// Stop the Torch instance.
/// </summary> /// </summary>
void Stop(); void Stop();
/// <summary>
/// Restart the Torch instance.
/// </summary>
void Restart();
/// <summary> /// <summary>
/// Initializes a save of the game. /// Initializes a save of the game.
/// </summary> /// </summary>
/// <param name="callerId">Id of the player who initiated the save.</param> /// <param name="callerId">Id of the player who initiated the save.</param>
void Save(long callerId); Task Save(long callerId);
/// <summary> /// <summary>
/// Initialize the Torch instance. /// Initialize the Torch instance.

View File

@@ -14,12 +14,12 @@ namespace Torch.API.Managers
/// <summary> /// <summary>
/// Fired when plugins are loaded. /// Fired when plugins are loaded.
/// </summary> /// </summary>
event Action<List<ITorchPlugin>> PluginsLoaded; event Action<IList<ITorchPlugin>> PluginsLoaded;
/// <summary> /// <summary>
/// Collection of loaded plugins. /// Collection of loaded plugins.
/// </summary> /// </summary>
ObservableCollection<ITorchPlugin> Plugins { get; } IList<ITorchPlugin> Plugins { get; }
/// <summary> /// <summary>
/// Updates all loaded plugins. /// Updates all loaded plugins.

View File

@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("1.0.186.642")] [assembly: AssemblyVersion("1.0.195.514")]
[assembly: AssemblyFileVersion("1.0.186.642")] [assembly: AssemblyFileVersion("1.0.195.514")]

View File

@@ -100,6 +100,8 @@ namespace Torch.Server
var waitProc = Process.GetProcessById(pid); var waitProc = Process.GetProcessById(pid);
_log.Warn($"Waiting for process {pid} to exit."); _log.Warn($"Waiting for process {pid} to exit.");
waitProc.WaitForExit(); waitProc.WaitForExit();
_log.Info("Continuing in 5 seconds.");
Thread.Sleep(5000);
} }
catch catch
{ {
@@ -245,8 +247,8 @@ quit";
}*/ }*/
_server = new TorchServer(config); _server = new TorchServer(config);
_server.Init();
_server.Init();
if (config.NoGui || config.Autostart) if (config.NoGui || config.Autostart)
{ {
new Thread(() => _server.Start()).Start(); new Thread(() => _server.Start()).Start();
@@ -255,7 +257,6 @@ quit";
if (!config.NoGui) if (!config.NoGui)
{ {
var ui = new TorchUI((TorchServer)_server); var ui = new TorchUI((TorchServer)_server);
ui.LoadConfig(config);
ui.ShowDialog(); ui.ShowDialog();
} }
} }

View File

@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("1.0.186.642")] [assembly: AssemblyVersion("1.0.195.514")]
[assembly: AssemblyFileVersion("1.0.186.642")] [assembly: AssemblyFileVersion("1.0.195.514")]

View File

@@ -24,7 +24,7 @@ namespace Torch.Server
public bool Update { get; set; } public bool Update { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[XmlIgnore, Arg("autostart", "Start the server immediately.")] [Arg("autostart", "Start the server immediately.")]
public bool Autostart { get; set; } public bool Autostart { get; set; }
/// <inheritdoc /> /// <inheritdoc />
@@ -32,7 +32,7 @@ namespace Torch.Server
public bool RestartOnCrash { get; set; } public bool RestartOnCrash { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[XmlIgnore, Arg("nogui", "Do not show the Torch UI.")] [Arg("nogui", "Do not show the Torch UI.")]
public bool NoGui { get; set; } public bool NoGui { get; set; }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -10,6 +10,7 @@ using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Principal; using System.Security.Principal;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Microsoft.Xml.Serialization.GeneratedAssembly; using Microsoft.Xml.Serialization.GeneratedAssembly;
using Sandbox.Engine.Analytics; using Sandbox.Engine.Analytics;
using Sandbox.Game.Multiplayer; using Sandbox.Game.Multiplayer;
@@ -20,6 +21,7 @@ using Torch.Managers;
using VRage.Dedicated; using VRage.Dedicated;
using VRage.FileSystem; using VRage.FileSystem;
using VRage.Game; using VRage.Game;
using VRage.Game.ModAPI;
using VRage.Game.ObjectBuilder; using VRage.Game.ObjectBuilder;
using VRage.Game.SessionComponents; using VRage.Game.SessionComponents;
using VRage.Library; using VRage.Library;
@@ -49,6 +51,7 @@ namespace Torch.Server
private float _simRatio; private float _simRatio;
private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false); private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false);
private Timer _watchdog; private Timer _watchdog;
private Stopwatch _uptime;
public TorchServer(TorchConfig config = null) public TorchServer(TorchConfig config = null)
{ {
@@ -133,6 +136,7 @@ namespace Torch.Server
if (State != ServerState.Stopped) if (State != ServerState.Stopped)
return; return;
_uptime = Stopwatch.StartNew();
IsRunning = true; IsRunning = true;
GameThread = Thread.CurrentThread; GameThread = Thread.CurrentThread;
Config.Save(); Config.Save();
@@ -166,7 +170,8 @@ namespace Torch.Server
{ {
base.Update(); base.Update();
SimulationRatio = Sync.ServerSimulationRatio; 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) if (_watchdog == null && Instance.Config.TickTimeout > 0)
{ {
@@ -215,15 +220,21 @@ namespace Torch.Server
IsRunning = false; IsRunning = false;
} }
public void Restart() /// <summary>
/// Restart the program. DOES NOT SAVE!
/// </summary>
public override void Restart()
{ {
var exe = Assembly.GetExecutingAssembly().Location;
((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
Process.Start(exe, Config.ToString());
Environment.Exit(0);
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Save(long callerId) public override Task Save(long callerId)
{ {
base.SaveGameAsync((statusCode) => SaveCompleted(statusCode, callerId)); return SaveGameAsync(statusCode => SaveCompleted(statusCode, callerId));
} }
/// <summary> /// <summary>
@@ -231,7 +242,7 @@ namespace Torch.Server
/// </summary> /// </summary>
/// <param name="statusCode">Return code of the save operation</param> /// <param name="statusCode">Return code of the save operation</param>
/// <param name="callerId">Caller of the save operation</param> /// <param name="callerId">Caller of the save operation</param>
private void SaveCompleted(SaveGameStatus statusCode, long callerId) private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
{ {
switch (statusCode) switch (statusCode)
{ {

View File

@@ -56,7 +56,7 @@ namespace Torch.Server.ViewModels
public SessionSettingsViewModel SessionSettings { get; } public SessionSettingsViewModel SessionSettings { get; }
public ObservableCollection<string> WorldPaths { get; } = new ObservableCollection<string>(); public ObservableList<string> WorldPaths { get; } = new ObservableList<string>();
private string _administrators; private string _administrators;
public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } } public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } }
private string _banned; private string _banned;

View File

@@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Blocks
public class BlockViewModel : EntityViewModel public class BlockViewModel : EntityViewModel
{ {
public IMyTerminalBlock Block { get; } public IMyTerminalBlock Block { get; }
public MTObservableCollection<PropertyViewModel> Properties { get; } = new MTObservableCollection<PropertyViewModel>(); public ObservableList<PropertyViewModel> Properties { get; } = new ObservableList<PropertyViewModel>();
public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}"; public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}";
@@ -24,8 +24,11 @@ namespace Torch.Server.ViewModels.Blocks
get => Block?.CustomName ?? "null"; get => Block?.CustomName ?? "null";
set set
{ {
TorchBase.Instance.InvokeBlocking(() => Block.CustomName = value); TorchBase.Instance.Invoke(() =>
OnPropertyChanged(); {
Block.CustomName = value;
OnPropertyChanged();
});
} }
} }
@@ -37,8 +40,11 @@ namespace Torch.Server.ViewModels.Blocks
get => ((MySlimBlock)Block.SlimBlock).BuiltBy; get => ((MySlimBlock)Block.SlimBlock).BuiltBy;
set set
{ {
TorchBase.Instance.InvokeBlocking(() => ((MySlimBlock)Block.SlimBlock).TransferAuthorship(value)); TorchBase.Instance.Invoke(() =>
OnPropertyChanged(); {
((MySlimBlock)Block.SlimBlock).TransferAuthorship(value);
OnPropertyChanged();
});
} }
} }

View File

@@ -16,17 +16,15 @@ namespace Torch.Server.ViewModels.Blocks
public T Value public T Value
{ {
get get => _prop.GetValue(Block.Block);
{
var val = default(T);
TorchBase.Instance.InvokeBlocking(() => val = _prop.GetValue(Block.Block));
return val;
}
set set
{ {
TorchBase.Instance.InvokeBlocking(() => _prop.SetValue(Block.Block, value)); TorchBase.Instance.Invoke(() =>
OnPropertyChanged(); {
Block.RefreshModel(); _prop.SetValue(Block.Block, value);
OnPropertyChanged();
Block.RefreshModel();
});
} }
} }

View File

@@ -40,6 +40,7 @@ namespace Torch.Server.ViewModels.Entities
public EntityViewModel(IMyEntity entity, EntityTreeViewModel tree) public EntityViewModel(IMyEntity entity, EntityTreeViewModel tree)
{ {
Entity = entity; Entity = entity;
Tree = tree;
} }
public EntityViewModel() public EntityViewModel()

View File

@@ -1,6 +1,9 @@
using System.Linq; using System;
using System.Linq;
using NLog; using NLog;
using Sandbox.Game.Entities; using Sandbox.Game.Entities;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using Torch.Server.ViewModels.Blocks; using Torch.Server.ViewModels.Blocks;
@@ -9,17 +12,16 @@ namespace Torch.Server.ViewModels.Entities
public class GridViewModel : EntityViewModel, ILazyLoad public class GridViewModel : EntityViewModel, ILazyLoad
{ {
private MyCubeGrid Grid => (MyCubeGrid)Entity; private MyCubeGrid Grid => (MyCubeGrid)Entity;
public MTObservableCollection<BlockViewModel> Blocks { get; } = new MTObservableCollection<BlockViewModel>(); public ObservableList<BlockViewModel> Blocks { get; } = new ObservableList<BlockViewModel>();
private static readonly Logger Log = LogManager.GetLogger(nameof(GridViewModel));
/// <inheritdoc /> /// <inheritdoc />
public string DescriptiveName => $"{Name} ({Grid.BlocksCount} blocks)"; public string DescriptiveName { get; }
public GridViewModel() { } public GridViewModel() { }
public GridViewModel(MyCubeGrid grid, EntityTreeViewModel tree) : base(grid, tree) 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)); Blocks.Add(new BlockViewModel(null, Tree));
} }
@@ -28,7 +30,6 @@ namespace Torch.Server.ViewModels.Entities
if (obj.FatBlock != null) if (obj.FatBlock != null)
Blocks.RemoveWhere(b => b.Block.EntityId == obj.FatBlock?.EntityId); Blocks.RemoveWhere(b => b.Block.EntityId == obj.FatBlock?.EntityId);
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
OnPropertyChanged(nameof(Name)); OnPropertyChanged(nameof(Name));
} }
@@ -36,9 +37,8 @@ namespace Torch.Server.ViewModels.Entities
{ {
var block = obj.FatBlock as IMyTerminalBlock; var block = obj.FatBlock as IMyTerminalBlock;
if (block != null) 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)); OnPropertyChanged(nameof(Name));
} }
@@ -48,20 +48,23 @@ namespace Torch.Server.ViewModels.Entities
if (_load) if (_load)
return; return;
Log.Debug($"Loading model {Grid.DisplayName}");
_load = true; _load = true;
Blocks.Clear(); Blocks.Clear();
TorchBase.Instance.InvokeBlocking(() => TorchBase.Instance.Invoke(() =>
{ {
foreach (var block in Grid.GetFatBlocks().Where(b => b is IMyTerminalBlock)) foreach (var block in Grid.GetFatBlocks().Where(b => b is IMyTerminalBlock))
{ {
Blocks.Add(new BlockViewModel((IMyTerminalBlock)block, Tree)); Blocks.Add(new BlockViewModel((IMyTerminalBlock)block, Tree));
} }
});
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
Grid.OnBlockAdded += Grid_OnBlockAdded; Grid.OnBlockAdded += Grid_OnBlockAdded;
Grid.OnBlockRemoved += Grid_OnBlockRemoved; Grid.OnBlockRemoved += Grid_OnBlockRemoved;
Tree.ControlDispatcher.BeginInvoke(() =>
{
Blocks.Sort(b => b.Block.CustomName);
});
});
} }
} }
} }

View File

@@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Entities
public override bool CanStop => false; public override bool CanStop => false;
public MTObservableCollection<GridViewModel> AttachedGrids { get; } = new MTObservableCollection<GridViewModel>(); public ObservableList<GridViewModel> AttachedGrids { get; } = new ObservableList<GridViewModel>();
public async Task UpdateAttachedGrids() public async Task UpdateAttachedGrids()
{ {

View File

@@ -3,22 +3,28 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Controls;
using Sandbox.Game.Entities; using Sandbox.Game.Entities;
using Sandbox.Game.Entities.Character; using Sandbox.Game.Entities.Character;
using Torch.Server.ViewModels.Entities; using Torch.Server.ViewModels.Entities;
using VRage.Game.ModAPI; using VRage.Game.ModAPI;
using VRage.ModAPI; using VRage.ModAPI;
using System.Windows.Threading;
using NLog;
namespace Torch.Server.ViewModels namespace Torch.Server.ViewModels
{ {
public class EntityTreeViewModel : ViewModel public class EntityTreeViewModel : ViewModel
{ {
public MTObservableCollection<GridViewModel> Grids { get; set; } = new MTObservableCollection<GridViewModel>(); //TODO: these should be sorted sets for speed
public MTObservableCollection<CharacterViewModel> Characters { get; set; } = new MTObservableCollection<CharacterViewModel>(); public ObservableList<GridViewModel> Grids { get; set; } = new ObservableList<GridViewModel>();
public MTObservableCollection<EntityViewModel> FloatingObjects { get; set; } = new MTObservableCollection<EntityViewModel>(); public ObservableList<CharacterViewModel> Characters { get; set; } = new ObservableList<CharacterViewModel>();
public MTObservableCollection<VoxelMapViewModel> VoxelMaps { get; set; } = new MTObservableCollection<VoxelMapViewModel>(); public ObservableList<EntityViewModel> FloatingObjects { get; set; } = new ObservableList<EntityViewModel>();
public ObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new ObservableList<VoxelMapViewModel>();
public Dispatcher ControlDispatcher => _control.Dispatcher;
private EntityViewModel _currentEntity; private EntityViewModel _currentEntity;
private UserControl _control;
public EntityViewModel CurrentEntity public EntityViewModel CurrentEntity
{ {
@@ -26,7 +32,12 @@ namespace Torch.Server.ViewModels
set { _currentEntity = value; OnPropertyChanged(); } set { _currentEntity = value; OnPropertyChanged(); }
} }
public EntityTreeViewModel() public EntityTreeViewModel(UserControl control)
{
_control = control;
}
public void Init()
{ {
MyEntities.OnEntityAdd += MyEntities_OnEntityAdd; MyEntities.OnEntityAdd += MyEntities_OnEntityAdd;
MyEntities.OnEntityRemove += MyEntities_OnEntityRemove; MyEntities.OnEntityRemove += MyEntities_OnEntityRemove;
@@ -56,20 +67,16 @@ namespace Torch.Server.ViewModels
switch (obj) switch (obj)
{ {
case MyCubeGrid grid: case MyCubeGrid grid:
if (Grids.All(g => g.Entity.EntityId != obj.EntityId)) Grids.Insert(new GridViewModel(grid, this), g => g.Name);
Grids.Add(new GridViewModel(grid, this));
break; break;
case MyCharacter character: case MyCharacter character:
if (Characters.All(g => g.Entity.EntityId != obj.EntityId)) Characters.Insert(new CharacterViewModel(character, this), c => c.Name);
Characters.Add(new CharacterViewModel(character, this));
break; break;
case MyFloatingObject floating: case MyFloatingObject floating:
if (FloatingObjects.All(g => g.Entity.EntityId != obj.EntityId)) FloatingObjects.Insert(new FloatingObjectViewModel(floating, this), f => f.Name);
FloatingObjects.Add(new FloatingObjectViewModel(floating, this));
break; break;
case MyVoxelBase voxel: case MyVoxelBase voxel:
if (VoxelMaps.All(g => g.Entity.EntityId != obj.EntityId)) VoxelMaps.Insert(new VoxelMapViewModel(voxel, this), v => v.Name);
VoxelMaps.Add(new VoxelMapViewModel(voxel, this));
break; break;
} }
} }

View File

@@ -11,7 +11,7 @@ namespace Torch.Server.ViewModels
{ {
public class PluginManagerViewModel : ViewModel public class PluginManagerViewModel : ViewModel
{ {
public MTObservableCollection<PluginViewModel> Plugins { get; } = new MTObservableCollection<PluginViewModel>(); public ObservableList<PluginViewModel> Plugins { get; } = new ObservableList<PluginViewModel>();
private PluginViewModel _selectedPlugin; private PluginViewModel _selectedPlugin;
public PluginViewModel SelectedPlugin public PluginViewModel SelectedPlugin
@@ -24,10 +24,12 @@ namespace Torch.Server.ViewModels
public PluginManagerViewModel(IPluginManager pluginManager) public PluginManagerViewModel(IPluginManager pluginManager)
{ {
foreach (var plugin in pluginManager)
Plugins.Add(new PluginViewModel(plugin));
pluginManager.PluginsLoaded += PluginManager_PluginsLoaded; pluginManager.PluginsLoaded += PluginManager_PluginsLoaded;
} }
private void PluginManager_PluginsLoaded(List<ITorchPlugin> obj) private void PluginManager_PluginsLoaded(IList<ITorchPlugin> obj)
{ {
Plugins.Clear(); Plugins.Clear();
foreach (var plugin in obj) foreach (var plugin in obj)

View File

@@ -35,7 +35,7 @@ namespace Torch.Server.ViewModels
BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value)); BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value));
} }
public MTObservableCollection<BlockLimitViewModel> BlockLimits { get; } = new MTObservableCollection<BlockLimitViewModel>(); public ObservableList<BlockLimitViewModel> BlockLimits { get; } = new ObservableList<BlockLimitViewModel>();
#region Multipliers #region Multipliers

View File

@@ -11,6 +11,7 @@
<TextBox x:Name="Message" DockPanel.Dock="Left" Margin="5,5,5,5" KeyDown="Message_OnKeyDown"></TextBox> <TextBox x:Name="Message" DockPanel.Dock="Left" Margin="5,5,5,5" KeyDown="Message_OnKeyDown"></TextBox>
</DockPanel> </DockPanel>
<ListView x:Name="ChatItems" ItemsSource="{Binding ChatHistory}" Margin="5,5,5,5"> <ListView x:Name="ChatItems" ItemsSource="{Binding ChatHistory}" Margin="5,5,5,5">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"/>
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<WrapPanel> <WrapPanel>

View File

@@ -41,8 +41,10 @@ namespace Torch.Server
{ {
_server = (TorchBase)server; _server = (TorchBase)server;
_multiplayer = (MultiplayerManager)server.Multiplayer; _multiplayer = (MultiplayerManager)server.Multiplayer;
ChatItems.Items.Clear();
DataContext = _multiplayer; 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) private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
@@ -74,22 +76,26 @@ namespace Torch.Server
return; return;
var commands = _server.Commands; var commands = _server.Commands;
string response = null;
if (commands.IsCommand(text)) if (commands.IsCommand(text))
{ {
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", 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 else
{ {
_server.Multiplayer.SendMessage(text); _server.Multiplayer.SendMessage(text);
} }
Message.Text = "";
}
private void OnMessageEntered_Callback(string response)
{
if (!string.IsNullOrEmpty(response)) if (!string.IsNullOrEmpty(response))
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response)); _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
Message.Text = "";
} }
} }
} }

View File

@@ -80,7 +80,7 @@
</DockPanel> </DockPanel>
<Button Content="Add" Margin="3" Click="AddLimit_OnClick" /> <Button Content="Add" Margin="3" Click="AddLimit_OnClick" />
<ListView ItemsSource="{Binding BlockLimits}" Margin="3"> <ListView ItemsSource="{Binding BlockLimits}" Margin="3">
<ListBox.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBox Text="{Binding BlockType}" Width="150" Margin="3" /> <TextBox Text="{Binding BlockType}" Width="150" Margin="3" />
@@ -88,7 +88,7 @@
<Button Content=" X " Margin="3" Click="RemoveLimit_OnClick" /> <Button Content=" X " Margin="3" Click="RemoveLimit_OnClick" />
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
</StackPanel> </StackPanel>
</Expander> </Expander>

View File

@@ -53,7 +53,6 @@ namespace Torch.Server.Views
Log.Info("Saved DS config."); Log.Info("Saved DS config.");
try try
{ {
//var checkpoint = MyLocalCache.LoadCheckpoint(Config.LoadWorld, out _);
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(Config.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes); MyObjectBuilderSerializer.DeserializeXML(Path.Combine(Config.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
if (checkpoint == null) if (checkpoint == null)
{ {

View File

@@ -8,9 +8,6 @@
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities" xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks" xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.DataContext>
<viewModels:EntityTreeViewModel />
</UserControl.DataContext>
<DockPanel> <DockPanel>
<DockPanel DockPanel.Dock="Left"> <DockPanel DockPanel.Dock="Left">
<StackPanel DockPanel.Dock="Bottom"> <StackPanel DockPanel.Dock="Bottom">

View File

@@ -27,12 +27,14 @@ namespace Torch.Server.Views
/// </summary> /// </summary>
public partial class EntitiesControl : UserControl public partial class EntitiesControl : UserControl
{ {
public EntityTreeViewModel Entities { get; set; } = new EntityTreeViewModel(); public EntityTreeViewModel Entities { get; set; }
public EntitiesControl() public EntitiesControl()
{ {
InitializeComponent(); InitializeComponent();
Entities = new EntityTreeViewModel(this);
DataContext = Entities; DataContext = Entities;
Entities.Init();
} }
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)

View File

@@ -47,6 +47,7 @@ namespace Torch.Server
Chat.BindServer(server); Chat.BindServer(server);
PlayerList.BindServer(server); PlayerList.BindServer(server);
Plugins.BindServer(server); Plugins.BindServer(server);
LoadConfig((TorchConfig)server.Config);
} }
public void LoadConfig(TorchConfig config) public void LoadConfig(TorchConfig config)

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
@@ -10,15 +9,18 @@ using System.Windows.Threading;
namespace Torch namespace Torch
{ {
[Obsolete("Use ObservableList<T>.")]
public class MTObservableCollection<T> : ObservableCollection<T> public class MTObservableCollection<T> : ObservableCollection<T>
{ {
public override event NotifyCollectionChangedEventHandler CollectionChanged; public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{ {
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged; NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
if (collectionChanged != null) 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 dispObj = nh.Target as DispatcherObject;
var dispatcher = dispObj?.Dispatcher; var dispatcher = dispObj?.Dispatcher;

View File

@@ -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
{
/// <summary>
/// An observable version of <see cref="List{T}"/>.
/// </summary>
public class ObservableList<T> : IList<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
private List<T> _internalList = new List<T>();
/// <inheritdoc />
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
/// <inheritdoc />
public void Clear()
{
_internalList.Clear();
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <inheritdoc />
public bool Contains(T item)
{
return _internalList.Contains(item);
}
/// <inheritdoc />
public void CopyTo(T[] array, int arrayIndex)
{
_internalList.CopyTo(array, arrayIndex);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public int Count => _internalList.Count;
/// <inheritdoc />
public bool IsReadOnly => false;
/// <inheritdoc />
public void Add(T item)
{
_internalList.Add(item);
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, Count - 1));
}
/// <inheritdoc />
public int IndexOf(T item) => _internalList.IndexOf(item);
/// <inheritdoc />
public void Insert(int index, T item)
{
_internalList.Insert(index, item);
OnPropertyChanged(nameof(Count));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
/// <summary>
/// Inserts an item in order based on the provided selector and comparer. This will only work properly on a pre-sorted list.
/// </summary>
public void Insert<TKey>(T item, Func<T, TKey> selector, IComparer<TKey> comparer = null)
{
comparer = comparer ?? Comparer<TKey>.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);
}
/// <inheritdoc />
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));
}
}
/// <summary>
/// Sorts the list using the given selector and comparer./>
/// </summary>
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
{
comparer = comparer ?? Comparer<TKey>.Default;
var sortedItems = _internalList.OrderBy(selector, comparer).ToList();
_internalList = sortedItems;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
/// Removes all items that satisfy the given condition.
/// </summary>
public void RemoveWhere(Func<T, bool> 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));
}
/// <inheritdoc />
public IEnumerator<T> GetEnumerator()
{
return _internalList.GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_internalList).GetEnumerator();
}
}
}

View File

@@ -6,7 +6,10 @@ using System.Text;
namespace Torch namespace Torch
{ {
public class CommandLine /// <summary>
/// Base class that adds tools for setting type properties through the command line.
/// </summary>
public abstract class CommandLine
{ {
private readonly string _argPrefix; private readonly string _argPrefix;
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>(); private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();

View File

@@ -21,7 +21,7 @@ namespace Torch.Commands
public CommandTree Commands { get; set; } = new CommandTree(); public CommandTree Commands { get; set; } = new CommandTree();
private Logger _log = LogManager.GetLogger(nameof(CommandManager)); 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; Prefix = prefix;
} }

View File

@@ -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>();
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.")] [Command("ver", "Shows the running Torch version.")]
[Permission(MyPromoteLevel.None)] [Permission(MyPromoteLevel.None)]
public void Version() public void Version()
@@ -62,11 +98,22 @@ namespace Torch.Commands
} }
[Command("stop", "Stops the server.")] [Command("stop", "Stops the server.")]
public void Stop() public void Stop(bool save = true)
{ {
Context.Respond("Stopping server."); Context.Respond("Stopping server.");
if (save)
Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait();
Context.Torch.Stop(); 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();
}
/// <summary> /// <summary>
/// Initializes a save of the game. /// Initializes a save of the game.

View File

@@ -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);
}
}
}

View File

@@ -16,6 +16,7 @@ using NLog;
using Torch; using Torch;
using Sandbox; using Sandbox;
using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Entities.Character;
using Sandbox.Game.Multiplayer; using Sandbox.Game.Multiplayer;
using Sandbox.Game.World; using Sandbox.Game.World;
using Sandbox.ModAPI; using Sandbox.ModAPI;
@@ -43,7 +44,7 @@ namespace Torch.Managers
/// <inheritdoc /> /// <inheritdoc />
public event MessageReceivedDel MessageReceived; public event MessageReceivedDel MessageReceived;
public MTObservableCollection<IChatMessage> ChatHistory { get; } = new MTObservableCollection<IChatMessage>(); public IList<IChatMessage> ChatHistory { get; } = new ObservableList<IChatMessage>();
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>(); public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer; public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager)); private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager));
@@ -99,6 +100,17 @@ namespace Torch.Managers
return p; return p;
} }
public ulong GetSteamId(long identityId)
{
foreach (var kv in _onlinePlayers)
{
if (kv.Value.Identity.IdentityId == identityId)
return kv.Key.SteamId;
}
return 0;
}
/// <inheritdoc /> /// <inheritdoc />
public string GetSteamUsername(ulong steamId) public string GetSteamUsername(ulong steamId)
{ {
@@ -108,17 +120,24 @@ namespace Torch.Managers
/// <inheritdoc /> /// <inheritdoc />
public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red) 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<CommandManager>(); var commands = Torch.GetManager<CommandManager>();
if (commands.IsCommand(message)) if (commands.IsCommand(message))
{ {
var response = commands.HandleCommandFromServer(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 else
{ {
var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message }; var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message };
MyMultiplayerBase.SendScriptedChatMessage(ref msg); 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<NetworkManager>().RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message);
} }
} }

View File

@@ -311,7 +311,7 @@ namespace Torch.Managers
var parameters = method.GetParameters(); var parameters = method.GetParameters();
for (var i = 0; i < parameters.Length; i++) 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()))}"); 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()))}");
} }

View File

@@ -23,9 +23,9 @@ namespace Torch.Managers
private UpdateManager _updateManager; private UpdateManager _updateManager;
/// <inheritdoc /> /// <inheritdoc />
public ObservableCollection<ITorchPlugin> Plugins { get; } = new ObservableCollection<ITorchPlugin>(); public IList<ITorchPlugin> Plugins { get; } = new ObservableList<ITorchPlugin>();
public event Action<List<ITorchPlugin>> PluginsLoaded; public event Action<IList<ITorchPlugin>> PluginsLoaded;
public PluginManager(ITorchBase torchInstance) : base(torchInstance) public PluginManager(ITorchBase torchInstance) : base(torchInstance)
{ {

View File

@@ -5,9 +5,24 @@
/// </summary> /// </summary>
public enum SaveGameStatus : byte public enum SaveGameStatus : byte
{ {
/// <summary>
/// The game was saved.
/// </summary>
Success = 0, Success = 0,
/// <summary>
/// A save operation is already in progress.
/// </summary>
SaveInProgress = 1, SaveInProgress = 1,
/// <summary>
/// The game is not in a save-able state.
/// </summary>
GameNotReady = 2, GameNotReady = 2,
/// <summary>
/// The save operation timed out.
/// </summary>
TimedOut = 3 TimedOut = 3
}; };
} }

View File

@@ -146,6 +146,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ChatMessage.cs" /> <Compile Include="ChatMessage.cs" />
<Compile Include="Collections\ObservableList.cs" />
<Compile Include="DispatcherExtensions.cs" />
<Compile Include="SaveGameStatus.cs" /> <Compile Include="SaveGameStatus.cs" />
<Compile Include="Collections\KeyTree.cs" /> <Compile Include="Collections\KeyTree.cs" />
<Compile Include="Collections\ObservableDictionary.cs" /> <Compile Include="Collections\ObservableDictionary.cs" />

View File

@@ -128,7 +128,7 @@ namespace Torch
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId; return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
} }
public async Task SaveGameAsync(Action<SaveGameStatus> callback) public Task SaveGameAsync(Action<SaveGameStatus> callback)
{ {
Log.Info("Saving game"); Log.Info("Saving game");
@@ -142,26 +142,17 @@ namespace Torch
} }
else else
{ {
using (var e = new AutoResetEvent(false)) var e = new AutoResetEvent(false);
{ MyAsyncSaving.Start(() => e.Set());
MyAsyncSaving.Start(() =>
{
MySector.ResetEyeAdaptation = true;
e.Set();
});
await Task.Run(() => return Task.Run(() =>
{ {
if (e.WaitOne(60000)) callback?.Invoke(e.WaitOne(5000) ? SaveGameStatus.Success : SaveGameStatus.TimedOut);
{ e.Dispose();
callback?.Invoke(SaveGameStatus.Success); });
return;
}
callback?.Invoke(SaveGameStatus.TimedOut);
}).ConfigureAwait(false);
}
} }
return Task.CompletedTask;
} }
#region Game Actions #region Game Actions
@@ -306,19 +297,28 @@ namespace Torch
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual void Save(long callerId) public virtual Task Save(long callerId)
{ {
return Task.CompletedTask;
} }
/// <inheritdoc /// <inheritdoc/>
public virtual void Start() public virtual void Start()
{ {
} }
/// <inheritdoc /> /// <inheritdoc />
public virtual void Stop() { } public virtual void Stop()
{
}
/// <inheritdoc />
public virtual void Restart()
{
}
/// <inheritdoc /> /// <inheritdoc />
public virtual void Dispose() public virtual void Dispose()