From e5d171107520aa5d40b00b8e3fb8cd26e32531e8 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期五, 13 三月 2026 12:39:13 +0800
Subject: [PATCH] feat: implement M region (Merker memory)

---
 Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Memory/MRegion.cs           |  168 ++++++++++++++++++++++++++++
 Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.UnitTests/Memory/MRegionTests.cs |  170 ++++++++++++++++++++++++++++
 2 files changed, 338 insertions(+), 0 deletions(-)

diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Memory/MRegion.cs b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Memory/MRegion.cs
new file mode 100644
index 0000000..963cdcc
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Memory/MRegion.cs
@@ -0,0 +1,168 @@
+using System;
+using WIDESEAWCS_S7Simulator.Core.Interfaces;
+
+namespace WIDESEAWCS_S7Simulator.Core.Memory
+{
+    /// <summary>
+    /// M鍖猴紙浣嶅瓨鍌ㄥ櫒/Merker锛夊疄鐜�
+    /// </summary>
+    public class MRegion : MemoryRegion, IMemoryRegion
+    {
+        /// <summary>
+        /// 鍖哄煙绫诲瀷
+        /// </summary>
+        public override string RegionType => "M";
+
+        /// <summary>
+        /// 鏋勯�犲嚱鏁�
+        /// </summary>
+        /// <param name="size">鍖哄煙澶у皬锛堝瓧鑺傦級</param>
+        public MRegion(int size) : base(size)
+        {
+        }
+
+        /// <summary>
+        /// 璇诲彇浣�
+        /// </summary>
+        /// <param name="byteOffset">瀛楄妭鍋忕Щ</param>
+        /// <param name="bitOffset">浣嶅亸绉伙紙0-7锛�</param>
+        /// <returns>浣嶅��</returns>
+        public bool ReadBit(ushort byteOffset, byte bitOffset)
+        {
+            if (bitOffset > 7)
+                throw new ArgumentOutOfRangeException(nameof(bitOffset), "浣嶅亸绉诲繀椤诲湪0-7涔嬮棿");
+
+            _lock.EnterReadLock();
+            try
+            {
+                if (byteOffset >= Size)
+                    throw new ArgumentOutOfRangeException(nameof(byteOffset), "瀛楄妭鍋忕Щ瓒呭嚭鑼冨洿");
+
+                return (_memory[byteOffset] & (1 << bitOffset)) != 0;
+            }
+            finally
+            {
+                _lock.ExitReadLock();
+            }
+        }
+
+        /// <summary>
+        /// 鍐欏叆浣�
+        /// </summary>
+        /// <param name="byteOffset">瀛楄妭鍋忕Щ</param>
+        /// <param name="bitOffset">浣嶅亸绉伙紙0-7锛�</param>
+        /// <param name="value">浣嶅��</param>
+        public void WriteBit(ushort byteOffset, byte bitOffset, bool value)
+        {
+            if (bitOffset > 7)
+                throw new ArgumentOutOfRangeException(nameof(bitOffset), "浣嶅亸绉诲繀椤诲湪0-7涔嬮棿");
+
+            _lock.EnterWriteLock();
+            try
+            {
+                if (byteOffset >= Size)
+                    throw new ArgumentOutOfRangeException(nameof(byteOffset), "瀛楄妭鍋忕Щ瓒呭嚭鑼冨洿");
+
+                if (value)
+                    _memory[byteOffset] |= (byte)(1 << bitOffset);
+                else
+                    _memory[byteOffset] &= (byte)~(1 << bitOffset);
+            }
+            finally
+            {
+                _lock.ExitWriteLock();
+            }
+        }
+
+        /// <summary>
+        /// 璇诲彇瀛楋紙Word锛�2瀛楄妭锛�
+        /// </summary>
+        public ushort ReadWord(ushort byteOffset)
+        {
+            var data = Read(byteOffset, 2);
+            return (ushort)((data[0] << 8) | data[1]);
+        }
+
+        /// <summary>
+        /// 鍐欏叆瀛楋紙Word锛�2瀛楄妭锛�
+        /// </summary>
+        public void WriteWord(ushort byteOffset, ushort value)
+        {
+            var data = new byte[] { (byte)(value >> 8), (byte)(value & 0xFF) };
+            Write(byteOffset, data);
+        }
+
+        /// <summary>
+        /// 璇诲彇鍙屽瓧锛圖Word锛�4瀛楄妭锛�
+        /// </summary>
+        public uint ReadDWord(ushort byteOffset)
+        {
+            var data = Read(byteOffset, 4);
+            return (uint)((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]);
+        }
+
+        /// <summary>
+        /// 鍐欏叆鍙屽瓧锛圖Word锛�4瀛楄妭锛�
+        /// </summary>
+        public void WriteDWord(ushort byteOffset, uint value)
+        {
+            var data = new byte[] {
+                (byte)(value >> 24),
+                (byte)((value >> 16) & 0xFF),
+                (byte)((value >> 8) & 0xFF),
+                (byte)(value & 0xFF)
+            };
+            Write(byteOffset, data);
+        }
+
+        /// <summary>
+        /// 璇诲彇鏁存暟锛圛nt锛�2瀛楄妭锛屾湁绗﹀彿锛�
+        /// </summary>
+        public short ReadInt(ushort byteOffset)
+        {
+            return (short)ReadWord(byteOffset);
+        }
+
+        /// <summary>
+        /// 鍐欏叆鏁存暟锛圛nt锛�2瀛楄妭锛屾湁绗﹀彿锛�
+        /// </summary>
+        public void WriteInt(ushort byteOffset, short value)
+        {
+            WriteWord(byteOffset, (ushort)value);
+        }
+
+        /// <summary>
+        /// 璇诲彇鍙屾暣鏁帮紙DInt锛�4瀛楄妭锛屾湁绗﹀彿锛�
+        /// </summary>
+        public int ReadDInt(ushort byteOffset)
+        {
+            return (int)ReadDWord(byteOffset);
+        }
+
+        /// <summary>
+        /// 鍐欏叆鍙屾暣鏁帮紙DInt锛�4瀛楄妭锛屾湁绗﹀彿锛�
+        /// </summary>
+        public void WriteDInt(ushort byteOffset, int value)
+        {
+            WriteDWord(byteOffset, (uint)value);
+        }
+
+        /// <summary>
+        /// 璇诲彇娴偣鏁帮紙Real锛�4瀛楄妭锛�
+        /// </summary>
+        public float ReadReal(ushort byteOffset)
+        {
+            var bytes = Read(byteOffset, 4);
+            return BitConverter.ToSingle(bytes, 0);
+        }
+
+        /// <summary>
+        /// 鍐欏叆娴偣鏁帮紙Real锛�4瀛楄妭锛�
+        /// </summary>
+        public void WriteReal(ushort byteOffset, float value)
+        {
+            var bytes = BitConverter.GetBytes(value);
+            Write(byteOffset, bytes);
+        }
+    }
+}
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.UnitTests/Memory/MRegionTests.cs b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.UnitTests/Memory/MRegionTests.cs
new file mode 100644
index 0000000..4e2baa9
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.UnitTests/Memory/MRegionTests.cs
@@ -0,0 +1,170 @@
+using Xunit;
+using WIDESEAWCS_S7Simulator.Core.Memory;
+
+namespace WIDESEAWCS_S7Simulator.UnitTests.Memory
+{
+    public class MRegionTests
+    {
+        [Fact]
+        public void Constructor_WithValidSize_CreatesRegion()
+        {
+            // Arrange & Act
+            var region = new MRegion(1024);
+
+            // Assert
+            Assert.Equal("M", region.RegionType);
+            Assert.Equal(1024, region.Size);
+        }
+
+        [Fact]
+        public void Read_WithinBounds_ReturnsData()
+        {
+            // Arrange
+            var region = new MRegion(1024);
+            var testData = new byte[] { 0x12, 0x34, 0x56, 0x78 };
+            region.Write(0, testData);
+
+            // Act
+            var result = region.Read(0, 4);
+
+            // Assert
+            Assert.Equal(testData, result);
+        }
+
+        [Fact]
+        public void Read_OutOfBounds_ThrowsArgumentOutOfRange()
+        {
+            // Arrange
+            var region = new MRegion(100);
+
+            // Act & Assert
+            Assert.Throws<ArgumentOutOfRangeException>(() => region.Read(0, 101));
+        }
+
+        [Fact]
+        public void Write_WithinBounds_WritesData()
+        {
+            // Arrange
+            var region = new MRegion(1024);
+            var testData = new byte[] { 0xAA, 0xBB, 0xCC, 0xDD };
+
+            // Act
+            region.Write(100, testData);
+            var result = region.Read(100, 4);
+
+            // Assert
+            Assert.Equal(testData, result);
+        }
+
+        [Fact]
+        public void Write_OutOfBounds_ThrowsArgumentOutOfRange()
+        {
+            // Arrange
+            var region = new MRegion(100);
+            var testData = new byte[] { 0x01, 0x02 };
+
+            // Act & Assert
+            Assert.Throws<ArgumentOutOfRangeException>(() => region.Write(99, testData));
+        }
+
+        [Fact]
+        public void ReadBit_ValidBit_ReturnsCorrectValue()
+        {
+            // Arrange
+            var region = new MRegion(1024);
+            region.Write(0, new byte[] { 0xFF }); // 鎵�鏈変綅涓�1
+
+            // Act
+            var result = region.ReadBit(0, 0);
+
+            // Assert
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void WriteBit_ValidBit_SetsCorrectValue()
+        {
+            // Arrange
+            var region = new MRegion(1024);
+
+            // Act
+            region.WriteBit(0, 3, true);
+            var result = region.ReadBit(0, 3);
+
+            // Assert
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void WriteBit_InvalidBitOffset_ThrowsArgumentOutOfRange()
+        {
+            // Arrange
+            var region = new MRegion(1024);
+
+            // Act & Assert
+            Assert.Throws<ArgumentOutOfRangeException>(() => region.WriteBit(0, 8, true));
+        }
+
+        [Fact]
+        public void Clear_ZerosAllMemory()
+        {
+            // Arrange
+            var region = new MRegion(100);
+            region.Write(0, new byte[] { 0xFF, 0xFF, 0xFF });
+
+            // Act
+            region.Clear();
+            var result = region.Read(0, 3);
+
+            // Assert
+            Assert.Equal(new byte[] { 0, 0, 0 }, result);
+        }
+
+        [Fact]
+        public void ConcurrentReadWrite_ThreadSafe()
+        {
+            // Arrange
+            var region = new MRegion(1024);
+            var exceptions = new System.Collections.Concurrent.ConcurrentBag<Exception>();
+            var cts = new CancellationTokenSource();
+            cts.CancelAfter(1000); // 1绉掑悗鍙栨秷
+
+            // Act
+            var writeTask = Task.Run(() =>
+            {
+                try
+                {
+                    var data = new byte[] { 0xAA, 0xBB };
+                    while (!cts.Token.IsCancellationRequested)
+                    {
+                        region.Write(0, data);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    exceptions.Add(ex);
+                }
+            }, cts.Token);
+
+            var readTask = Task.Run(() =>
+            {
+                try
+                {
+                    while (!cts.Token.IsCancellationRequested)
+                    {
+                        region.Read(0, 2);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    exceptions.Add(ex);
+                }
+            }, cts.Token);
+
+            Task.WaitAll(writeTask, readTask);
+
+            // Assert
+            Assert.Empty(exceptions);
+        }
+    }
+}

--
Gitblit v1.9.3