磁导航1.0内部交管和信号交互
This commit is contained in:
@@ -0,0 +1,630 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace StandardScene.MagCarSimulator
|
||||
{
|
||||
public sealed class MainForm : Form
|
||||
{
|
||||
private const int MaxLogLines = 400;
|
||||
private const int MaxPendingLogs = 300;
|
||||
|
||||
private readonly MagCarSimConfig _config;
|
||||
private readonly MagCarSimHost _host;
|
||||
private readonly StringBuilder _logBuffer = new StringBuilder();
|
||||
private readonly Queue<string> _pendingLogs = new Queue<string>();
|
||||
private readonly object _logLock = new object();
|
||||
private readonly Timer _uiTimer;
|
||||
private bool _logFlushScheduled;
|
||||
private int _logLineCount;
|
||||
|
||||
private TextBox _mapPathBox;
|
||||
private CheckBox _useTagValueBox;
|
||||
private Label _mapInfoLabel;
|
||||
private DataGridView _grid;
|
||||
private ListBox _siteList;
|
||||
private TextBox _logBox;
|
||||
private Button _listenButton;
|
||||
private Button _loopButton;
|
||||
private Button _stopButton;
|
||||
private Button _releaseButton;
|
||||
private Button _addButton;
|
||||
private Button _removeButton;
|
||||
private Button _saveButton;
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
_config = MagCarSimConfig.Load();
|
||||
MagCarSimLog.Configure(_config);
|
||||
_host = new MagCarSimHost(_config);
|
||||
|
||||
Text = "MagCar 模拟器(FASS 1.0 / SimpleLite 地图循环)";
|
||||
Width = 1280;
|
||||
Height = 820;
|
||||
MinimumSize = new Size(980, 640);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Font = new Font("Microsoft YaHei UI", 9F);
|
||||
|
||||
BuildLayout();
|
||||
LoadConfigToUi();
|
||||
|
||||
MagCarSimLog.MessageWritten += OnLogMessage;
|
||||
_uiTimer = new Timer { Interval = 250 };
|
||||
_uiTimer.Tick += (_, __) => RefreshGridStatus();
|
||||
_uiTimer.Start();
|
||||
|
||||
FormClosed += (_, __) =>
|
||||
{
|
||||
MagCarSimLog.MessageWritten -= OnLogMessage;
|
||||
_uiTimer.Stop();
|
||||
_host.Dispose();
|
||||
MagCarSimLog.Shutdown();
|
||||
};
|
||||
|
||||
AppendLog($"就绪。文件日志目录: {MagCarSimLog.GetLogDirectory()}。加载 SimpleLite 地图后启动监听,再开始循环。普通站可叠车;停止点和交管点同时只允许一辆。");
|
||||
TryLoadMap(_config.MapPath, silent: true);
|
||||
}
|
||||
|
||||
private void BuildLayout()
|
||||
{
|
||||
var root = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 1,
|
||||
RowCount = 4,
|
||||
Padding = new Padding(8)
|
||||
};
|
||||
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
root.RowStyles.Add(new RowStyle(SizeType.Percent, 42));
|
||||
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
root.RowStyles.Add(new RowStyle(SizeType.Percent, 58));
|
||||
Controls.Add(root);
|
||||
|
||||
root.Controls.Add(BuildToolbar(), 0, 0);
|
||||
_grid = BuildGrid();
|
||||
root.Controls.Add(_grid, 0, 1);
|
||||
root.Controls.Add(BuildVehicleButtons(), 0, 2);
|
||||
|
||||
var split = new SplitContainer
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Orientation = Orientation.Vertical,
|
||||
SplitterDistance = 280
|
||||
};
|
||||
_siteList = new ListBox { Dock = DockStyle.Fill, IntegralHeight = false };
|
||||
var sitePanel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(0, 4, 4, 0) };
|
||||
sitePanel.Controls.Add(_siteList);
|
||||
sitePanel.Controls.Add(new Label
|
||||
{
|
||||
Text = "地图站点(带 [停止] 的为 Mag_NeedStop)",
|
||||
Dock = DockStyle.Top,
|
||||
Height = 22
|
||||
});
|
||||
split.Panel1.Controls.Add(sitePanel);
|
||||
|
||||
_logBox = new TextBox
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Multiline = true,
|
||||
ReadOnly = true,
|
||||
ScrollBars = ScrollBars.Both,
|
||||
WordWrap = false,
|
||||
Font = new Font("Consolas", 9F)
|
||||
};
|
||||
var logPanel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(4, 4, 0, 0) };
|
||||
logPanel.Controls.Add(_logBox);
|
||||
logPanel.Controls.Add(new Label
|
||||
{
|
||||
Text = "协议 / 运动日志",
|
||||
Dock = DockStyle.Top,
|
||||
Height = 22
|
||||
});
|
||||
split.Panel2.Controls.Add(logPanel);
|
||||
root.Controls.Add(split, 0, 3);
|
||||
}
|
||||
|
||||
private Control BuildToolbar()
|
||||
{
|
||||
var panel = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
ColumnCount = 8,
|
||||
RowCount = 2,
|
||||
Padding = new Padding(0, 0, 0, 6)
|
||||
};
|
||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
|
||||
panel.Controls.Add(new Label { Text = "地图", AutoSize = true, Anchor = AnchorStyles.Left }, 0, 0);
|
||||
_mapPathBox = new TextBox { Dock = DockStyle.Fill, Width = 480 };
|
||||
panel.Controls.Add(_mapPathBox, 1, 0);
|
||||
|
||||
var browse = new Button { Text = "浏览…", AutoSize = true };
|
||||
browse.Click += (_, __) => BrowseMap();
|
||||
panel.Controls.Add(browse, 2, 0);
|
||||
|
||||
var load = new Button { Text = "加载地图", AutoSize = true };
|
||||
load.Click += (_, __) => TryLoadMap(_mapPathBox.Text, silent: false);
|
||||
panel.Controls.Add(load, 3, 0);
|
||||
|
||||
_listenButton = new Button { Text = "启动监听", AutoSize = true };
|
||||
_listenButton.Click += (_, __) => StartListen();
|
||||
panel.Controls.Add(_listenButton, 4, 0);
|
||||
|
||||
_loopButton = new Button { Text = "开始循环", AutoSize = true };
|
||||
_loopButton.Click += (_, __) => StartLoop();
|
||||
panel.Controls.Add(_loopButton, 5, 0);
|
||||
|
||||
_stopButton = new Button { Text = "停止模拟", AutoSize = true, Enabled = false };
|
||||
_stopButton.Click += (_, __) => StopSim();
|
||||
panel.Controls.Add(_stopButton, 6, 0);
|
||||
|
||||
_saveButton = new Button { Text = "保存配置", AutoSize = true };
|
||||
_saveButton.Click += (_, __) => SaveConfig();
|
||||
panel.Controls.Add(_saveButton, 7, 0);
|
||||
|
||||
_useTagValueBox = new CheckBox { Text = "用 TagValue 作为节点号", AutoSize = true, Anchor = AnchorStyles.Left };
|
||||
panel.SetColumnSpan(_useTagValueBox, 2);
|
||||
panel.Controls.Add(_useTagValueBox, 0, 1);
|
||||
|
||||
_mapInfoLabel = new Label
|
||||
{
|
||||
Text = "未加载地图",
|
||||
AutoSize = true,
|
||||
Anchor = AnchorStyles.Left,
|
||||
ForeColor = Color.DimGray
|
||||
};
|
||||
panel.SetColumnSpan(_mapInfoLabel, 6);
|
||||
panel.Controls.Add(_mapInfoLabel, 2, 1);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Control BuildVehicleButtons()
|
||||
{
|
||||
var panel = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
AutoSize = true,
|
||||
WrapContents = false,
|
||||
Padding = new Padding(0, 4, 0, 4)
|
||||
};
|
||||
_addButton = new Button { Text = "添加车辆", AutoSize = true };
|
||||
_addButton.Click += (_, __) => AddVehicleRow();
|
||||
_removeButton = new Button { Text = "删除选中", AutoSize = true };
|
||||
_removeButton.Click += (_, __) => RemoveSelectedVehicle();
|
||||
_releaseButton = new Button { Text = "放行选中车", AutoSize = true };
|
||||
_releaseButton.Click += (_, __) => ReleaseSelected();
|
||||
panel.Controls.Add(_addButton);
|
||||
panel.Controls.Add(_removeButton);
|
||||
panel.Controls.Add(_releaseButton);
|
||||
panel.Controls.Add(new Label
|
||||
{
|
||||
Text = "普通站可叠车。停止点和交管点同时只允许一辆,后车停在上一站保持运行中。仅 Mag_NeedStop 停车等 0x01。",
|
||||
AutoSize = true,
|
||||
Padding = new Padding(12, 8, 0, 0),
|
||||
ForeColor = Color.DimGray
|
||||
});
|
||||
return panel;
|
||||
}
|
||||
|
||||
private DataGridView BuildGrid()
|
||||
{
|
||||
var grid = new DataGridView
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
AllowUserToAddRows = false,
|
||||
AllowUserToDeleteRows = false,
|
||||
RowHeadersVisible = false,
|
||||
SelectionMode = DataGridViewSelectionMode.FullRowSelect,
|
||||
MultiSelect = false,
|
||||
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
|
||||
BackgroundColor = Color.White
|
||||
};
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Name", HeaderText = "名称", FillWeight = 80 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Code", HeaderText = "车号", FillWeight = 50 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Port", HeaderText = "端口", FillWeight = 55 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Start", HeaderText = "起点", FillWeight = 50 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "End", HeaderText = "终点", FillWeight = 50 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Interval", HeaderText = "间隔ms", FillWeight = 60 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Site", HeaderText = "当前站", ReadOnly = true, FillWeight = 55 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Node", HeaderText = "节点", ReadOnly = true, FillWeight = 50 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "State", HeaderText = "状态", ReadOnly = true, FillWeight = 70 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Wait", HeaderText = "等待", ReadOnly = true, FillWeight = 55 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Clients", HeaderText = "连接", ReadOnly = true, FillWeight = 45 });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn { Name = "Path", HeaderText = "循环路径", ReadOnly = true, FillWeight = 160 });
|
||||
return grid;
|
||||
}
|
||||
|
||||
private void LoadConfigToUi()
|
||||
{
|
||||
_mapPathBox.Text = _config.MapPath ?? "";
|
||||
_useTagValueBox.Checked = _config.UseTagValueAsNode;
|
||||
_grid.Rows.Clear();
|
||||
foreach (var vehicle in _config.Vehicles)
|
||||
{
|
||||
_grid.Rows.Add(
|
||||
vehicle.Name,
|
||||
vehicle.VehicleCode,
|
||||
vehicle.ListenPort,
|
||||
vehicle.StartSiteId,
|
||||
vehicle.EndSiteId,
|
||||
vehicle.IntervalMs,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
private List<MagCarSimVehicleConfig> ReadVehiclesFromGrid()
|
||||
{
|
||||
var list = new List<MagCarSimVehicleConfig>();
|
||||
foreach (DataGridViewRow row in _grid.Rows)
|
||||
{
|
||||
if (row.IsNewRow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var code = ToUShort(row.Cells["Code"].Value, 1);
|
||||
list.Add(new MagCarSimVehicleConfig
|
||||
{
|
||||
Name = Convert.ToString(row.Cells["Name"].Value),
|
||||
VehicleCode = code,
|
||||
ListenPort = ToInt(row.Cells["Port"].Value, 5001),
|
||||
StartSiteId = ToInt(row.Cells["Start"].Value, 1),
|
||||
EndSiteId = ToInt(row.Cells["End"].Value, 2),
|
||||
IntervalMs = ToInt(row.Cells["Interval"].Value, _config.DefaultIntervalMs),
|
||||
LoopSiteIds = PreserveLoop(code)
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<int> PreserveLoop(ushort vehicleCode)
|
||||
{
|
||||
var existing = _config.Vehicles?.Find(v => v.VehicleCode == vehicleCode);
|
||||
if (existing?.LoopSiteIds == null || existing.LoopSiteIds.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new List<int>(existing.LoopSiteIds);
|
||||
}
|
||||
|
||||
private void BrowseMap()
|
||||
{
|
||||
using var dialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "SimpleLite 地图 (*.json)|*.json|所有文件 (*.*)|*.*",
|
||||
Title = "选择 SimpleLite 地图"
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(_mapPathBox.Text) && File.Exists(_mapPathBox.Text))
|
||||
{
|
||||
dialog.InitialDirectory = Path.GetDirectoryName(_mapPathBox.Text);
|
||||
dialog.FileName = Path.GetFileName(_mapPathBox.Text);
|
||||
}
|
||||
|
||||
if (dialog.ShowDialog(this) == DialogResult.OK)
|
||||
{
|
||||
_mapPathBox.Text = dialog.FileName;
|
||||
TryLoadMap(dialog.FileName, silent: false);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryLoadMap(string path, bool silent)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
if (!silent)
|
||||
{
|
||||
MessageBox.Show(this, "请先选择 SimpleLite 地图文件。", "加载地图", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_config.UseTagValueAsNode = _useTagValueBox.Checked;
|
||||
var map = _host.LoadMap(path);
|
||||
_mapPathBox.Text = map.FilePath;
|
||||
RefreshSiteList(map);
|
||||
var stops = map.ListNeedStopSiteIds();
|
||||
_mapInfoLabel.Text = $"站点 {map.Sites.Count},路径 {map.Tracks.Count},停止点 {(stops.Count == 0 ? "无(循环将一直过站)" : string.Join(",", stops))}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_mapInfoLabel.Text = "地图加载失败";
|
||||
if (!silent)
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "加载地图失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendLog("地图未加载:" + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshSiteList(SimpleLiteMap map)
|
||||
{
|
||||
_siteList.Items.Clear();
|
||||
foreach (var site in map.Sites.Values)
|
||||
{
|
||||
var tag = site.NeedStop ? " [停止]" : "";
|
||||
var node = map.ResolveNode(site.Id);
|
||||
_siteList.Items.Add($"站 {site.Id} node={node}{tag}");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartListen()
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyGridToHost();
|
||||
_host.StartListen();
|
||||
SetRunningUi(true, looping: false);
|
||||
AppendLog("TCP 监听已启动。请在 SimpleLite 中把 MagCar 的 address/Port/VehicleCode 配成与上表一致。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "启动监听失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_host.IsListening)
|
||||
{
|
||||
ApplyGridToHost();
|
||||
}
|
||||
|
||||
_host.StartLoops();
|
||||
SetRunningUi(true, looping: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "开始循环失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopSim()
|
||||
{
|
||||
_host.StopAll();
|
||||
SetRunningUi(false, looping: false);
|
||||
AppendLog("模拟已停止。");
|
||||
RefreshGridStatus();
|
||||
}
|
||||
|
||||
private void ApplyGridToHost()
|
||||
{
|
||||
_config.MapPath = _mapPathBox.Text;
|
||||
_config.UseTagValueAsNode = _useTagValueBox.Checked;
|
||||
_config.Vehicles = ReadVehiclesFromGrid();
|
||||
if (!string.IsNullOrWhiteSpace(_config.MapPath))
|
||||
{
|
||||
TryLoadMap(_config.MapPath, silent: false);
|
||||
}
|
||||
|
||||
_host.RebuildVehicles(_config.Vehicles);
|
||||
}
|
||||
|
||||
private void AddVehicleRow()
|
||||
{
|
||||
if (_host.IsListening)
|
||||
{
|
||||
MessageBox.Show(this, "运行中不能改车辆列表,请先停止模拟。", "添加车辆", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var nextCode = _grid.Rows.Count + 1;
|
||||
_grid.Rows.Add($"AGV-{nextCode}", nextCode, 5000 + nextCode, 1, 6, _config.DefaultIntervalMs, "", "", "", "", "", "");
|
||||
}
|
||||
|
||||
private void RemoveSelectedVehicle()
|
||||
{
|
||||
if (_host.IsListening)
|
||||
{
|
||||
MessageBox.Show(this, "运行中不能改车辆列表,请先停止模拟。", "删除车辆", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_grid.CurrentRow == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_grid.Rows.Remove(_grid.CurrentRow);
|
||||
}
|
||||
|
||||
private void ReleaseSelected()
|
||||
{
|
||||
if (_grid.CurrentRow == null)
|
||||
{
|
||||
MessageBox.Show(this, "请先选中一辆车。", "放行", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var code = ToUShort(_grid.CurrentRow.Cells["Code"].Value, 0);
|
||||
if (!_host.TryRelease(code))
|
||||
{
|
||||
MessageBox.Show(this, $"未找到车号 {code},请先启动监听。", "放行", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
_config.MapPath = _mapPathBox.Text;
|
||||
_config.UseTagValueAsNode = _useTagValueBox.Checked;
|
||||
_config.Vehicles = ReadVehiclesFromGrid();
|
||||
_config.Save();
|
||||
AppendLog("配置已保存到 " + MagCarSimConfig.SettingsPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "保存失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetRunningUi(bool listening, bool looping)
|
||||
{
|
||||
_listenButton.Enabled = !listening;
|
||||
_loopButton.Enabled = !looping;
|
||||
_stopButton.Enabled = listening;
|
||||
_addButton.Enabled = !listening;
|
||||
_removeButton.Enabled = !listening;
|
||||
_grid.ReadOnly = listening;
|
||||
_useTagValueBox.Enabled = !listening;
|
||||
}
|
||||
|
||||
private void RefreshGridStatus()
|
||||
{
|
||||
var snapshots = _host.Snapshots();
|
||||
if (snapshots.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _grid.Rows.Count && i < snapshots.Count; i++)
|
||||
{
|
||||
var snap = snapshots[i];
|
||||
var row = _grid.Rows[i];
|
||||
row.Cells["Site"].Value = snap.CurrentSiteId;
|
||||
row.Cells["Node"].Value = snap.Node;
|
||||
row.Cells["State"].Value = snap.StateText;
|
||||
row.Cells["Wait"].Value = snap.WaitingRelease ? "等放行" : (snap.HoldingForStop ? "让行" : "");
|
||||
row.Cells["Clients"].Value = snap.ClientCount;
|
||||
row.Cells["Path"].Value = snap.PathText;
|
||||
row.DefaultCellStyle.BackColor = snap.WaitingRelease
|
||||
? Color.FromArgb(255, 236, 179)
|
||||
: snap.HoldingForStop
|
||||
? Color.FromArgb(207, 232, 255)
|
||||
: Color.White;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLogMessage(string message)
|
||||
{
|
||||
lock (_logLock)
|
||||
{
|
||||
while (_pendingLogs.Count >= MaxPendingLogs)
|
||||
{
|
||||
_pendingLogs.Dequeue();
|
||||
}
|
||||
|
||||
_pendingLogs.Enqueue(message);
|
||||
}
|
||||
|
||||
if (_logFlushScheduled || !IsHandleCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logFlushScheduled = true;
|
||||
try
|
||||
{
|
||||
BeginInvoke(new Action(FlushLogs));
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logFlushScheduled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void FlushLogs()
|
||||
{
|
||||
_logFlushScheduled = false;
|
||||
List<string> batch;
|
||||
lock (_logLock)
|
||||
{
|
||||
if (_pendingLogs.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
batch = new List<string>(_pendingLogs.Count);
|
||||
while (_pendingLogs.Count > 0)
|
||||
{
|
||||
batch.Add(_pendingLogs.Dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var line in batch)
|
||||
{
|
||||
_logLineCount++;
|
||||
_logBuffer.AppendLine(line);
|
||||
}
|
||||
|
||||
while (_logLineCount > MaxLogLines)
|
||||
{
|
||||
var text = _logBuffer.ToString();
|
||||
var firstBreak = text.IndexOf('\n');
|
||||
if (firstBreak < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_logBuffer.Remove(0, firstBreak + 1);
|
||||
_logLineCount--;
|
||||
}
|
||||
|
||||
_logBox.Text = _logBuffer.ToString();
|
||||
_logBox.SelectionStart = _logBox.TextLength;
|
||||
_logBox.ScrollToCaret();
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
lock (_logLock)
|
||||
{
|
||||
while (_pendingLogs.Count >= MaxPendingLogs)
|
||||
{
|
||||
_pendingLogs.Dequeue();
|
||||
}
|
||||
|
||||
_pendingLogs.Enqueue(message);
|
||||
}
|
||||
|
||||
if (IsHandleCreated)
|
||||
{
|
||||
if (!_logFlushScheduled)
|
||||
{
|
||||
_logFlushScheduled = true;
|
||||
BeginInvoke(new Action(FlushLogs));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FlushLogs();
|
||||
}
|
||||
}
|
||||
|
||||
private static int ToInt(object value, int fallback)
|
||||
{
|
||||
return int.TryParse(Convert.ToString(value), out var n) ? n : fallback;
|
||||
}
|
||||
|
||||
private static ushort ToUShort(object value, ushort fallback)
|
||||
{
|
||||
return ushort.TryParse(Convert.ToString(value), out var n) ? n : fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user