631 lines
23 KiB
C#
631 lines
23 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|