Imported newly created Nuclex.Support library which contains various supporting classes not related to a specific topic

git-svn-id: file:///srv/devel/repo-conversion/nusu@1 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2007-02-28 20:20:50 +00:00
commit ce8df64be5
10 changed files with 1043 additions and 0 deletions

114
Nuclex.Support.csproj Normal file
View File

@ -0,0 +1,114 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{A696702E-AFAD-45E7-88FA-1E2520E5E746}</ProjectGuid>
<ProjectTypeGuids>{9F340DF3-2AED-4330-AC16-78AC2D9B4738};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Nuclex.Support</RootNamespace>
<AssemblyName>Nuclex.Support</AssemblyName>
<XnaFrameworkVersion>v1.0</XnaFrameworkVersion>
<XnaPlatform>Windows</XnaPlatform>
<XNAGlobalContentPipelineAssemblies>Microsoft.Xna.Framework.Content.Pipeline.EffectImporter.dll;Microsoft.Xna.Framework.Content.Pipeline.FBXImporter.dll;Microsoft.Xna.Framework.Content.Pipeline.TextureImporter.dll;Microsoft.Xna.Framework.Content.Pipeline.XImporter.dll</XNAGlobalContentPipelineAssemblies>
<XNAProjectContentPipelineAssemblies>
</XNAProjectContentPipelineAssemblies>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\x86\Debug</OutputPath>
<DefineConstants>TRACE;DEBUG;UNITTEST</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib>
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>x86</PlatformTarget>
<DocumentationFile>Documentation\Nuclex.Support.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\x86\Release</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib>
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>x86</PlatformTarget>
<DocumentationFile>Documentation\Nuclex.Support.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Xna.Framework">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.Xna.Framework.Game">
<Private>False</Private>
</Reference>
<Reference Include="mscorlib">
<Private>False</Private>
</Reference>
<Reference Include="nmock, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>References\nmock\net-2.0\nmock.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=2.2.9.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>References\nunit\net-2.0\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System">
<Private>False</Private>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Source\Collections\Parentable.cs">
<XNAUseContentPipeline>false</XNAUseContentPipeline>
<Name>Parentable</Name>
</Compile>
<Compile Include="Source\Collections\ParentingCollection.cs">
<XNAUseContentPipeline>false</XNAUseContentPipeline>
<Name>ParentingCollection</Name>
</Compile>
<Compile Include="Source\Collections\PriorityQueue.cs">
<XNAUseContentPipeline>false</XNAUseContentPipeline>
<Name>PriorityQueue</Name>
</Compile>
<Compile Include="Source\Collections\RingMemoryStream.cs">
<XNAUseContentPipeline>false</XNAUseContentPipeline>
<Name>RingMemoryStream</Name>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Source\Collections\RingMemoryStream.Test.cs">
<XNAUseContentPipeline>false</XNAUseContentPipeline>
<Name>RingMemoryStream.Test</Name>
<DependentUpon>RingMemoryStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\Serialization\BinarySerializer.Test.cs">
<XNAUseContentPipeline>false</XNAUseContentPipeline>
<Name>BinarySerializer.Test</Name>
<DependentUpon>BinarySerializer.cs</DependentUpon>
</Compile>
<Compile Include="Source\Serialization\IBinarySerializable.cs">
<XNAUseContentPipeline>false</XNAUseContentPipeline>
<Name>IBinarySerializable</Name>
</Compile>
<Compile Include="Source\Serialization\BinarySerializer.cs">
<XNAUseContentPipeline>false</XNAUseContentPipeline>
<Name>BinarySerializer</Name>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA\Game Studio Express\v1.0\Microsoft.Xna.ContentPipeline.targets" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA\Game Studio Express\v1.0\Microsoft.Xna.Common.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,31 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Nuclex.Support")]
[assembly: AssemblyProduct("Nuclex.Support")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("Nuclex Development Labs")]
[assembly: AssemblyCopyright("Copyright © Nuclex Development Labs 2007")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1308e4c3-a0c1-423a-aaae-61c7314777e0")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
/// <summary>Base class for objects that can be parented to an owner</summary>
/// <typeparam name="ParentType">Type of the parent object</typeparam>
public class Parentable<ParentType>
where ParentType : class {
/// <summary>Assigns a new parent to this instance</summary>
internal void SetParent(ParentType parent) {
ParentType oldParent = this.parent;
this.parent = parent;
OnParentChanged(oldParent);
}
/// <summary>The parent object that owns this instance</summary>
protected ParentType Parent {
get { return this.parent; }
}
/// <summary>Invoked whenever the instance's owner changes</summary>
/// <remarks>
/// When items are parented for the first time, the oldParent argument will
/// be null. Also, if the element is removed from the collection, the
/// current parent will be null.
/// </remarks>
/// <param name="oldParent">Previous owner of the instance</param>
protected virtual void OnParentChanged(ParentType oldParent) { }
/// <summary>Current parent of this object</summary>
private ParentType parent;
}
} // namespace Nuclex.Support.Collections

View File

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
namespace Nuclex.Support.Collections {
/// <summary>Collection that automatically assigns an owner to all its elements</summary>
/// <remarks>
/// This collection automatically assigns a parent object to elements that
/// are managed in it. The elements have to derive from the Parentable&lt;&gt;
/// base class.
/// </remarks>
/// <typeparam name="ParentType">Type of the parent object to assign to items</typeparam>
/// <typeparam name="ItemType">Type of the items being managed in the collection</typeparam>
public class ParentingCollection<ParentType, ItemType> : Collection<ItemType>, IDisposable
where ItemType : Parentable<ParentType>
where ParentType : class {
/// <summary>Called when the object is garbage-collected</summary>
~ParentingCollection() {
Dispose(false); // called from GC
}
/// <summary>Reparents all elements in the collection</summary>
/// <param name="parent">New parent to take ownership of the items</param>
internal void Reparent(ParentType parent) {
this.parent = parent;
for(int index = 0; index < Count; ++index)
base[index].SetParent(parent);
}
/// <summary>Called when the asset needs to release its resources</summary>
/// <param name="calledByUser">
/// Whether the mehod has been called from user code. If this argument
/// is false, the object is being disposed by the garbage collection and
/// it mustn't access other objects (including the attempt to Dispose() them)
/// as these might have already been destroyed by the GC.
/// </param>
protected virtual void Dispose(bool calledByUser) {
// Only destroy the other resources when we're not being called from
// the garbage collector, otherwise we'd risk accessing objects that
// have already been disposed
if(calledByUser) {
// Have the items do their cleanup work
Reparent(null);
// Dispose of all the items in the collection
foreach(ItemType item in this) {
IDisposable disposable = item as IDisposable;
if(disposable != null)
disposable.Dispose();
}
// Remove all items from the collection
base.ClearItems();
}
}
/// <summary>Clears all elements from the collection</summary>
protected override void ClearItems() {
for(int index = 0; index < Count; ++index)
base[index].SetParent(null);
base.ClearItems();
}
/// <summary>Inserts a new element into the collection</summary>
/// <param name="index">Index at which to insert the element</param>
/// <param name="item">Item to be inserted</param>
protected override void InsertItem(int index, ItemType item) {
base.InsertItem(index, item);
item.SetParent(this.parent);
}
/// <summary>Removes an element from the collection</summary>
/// <param name="index">Index of the element to remove</param>
protected override void RemoveItem(int index) {
base[index].SetParent(null);
base.RemoveItem(index);
}
/// <summary>Takes over a new element that is directly assigned</summary>
/// <param name="index">Index of the element that was assigned</param>
/// <param name="item">New item</param>
protected override void SetItem(int index, ItemType item) {
base.SetItem(index, item);
item.SetParent(this.parent);
}
/// <summary>Release all resources owned by the instance explicitely</summary>
void IDisposable.Dispose() {
Dispose(true); // Called by user
}
/// <summary>Parent this collection currently belongs to</summary>
private ParentType parent;
}
} // namespace Nuclex.Support.Collections

View File

@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.Collections;
namespace Nuclex.Support.Collections {
/*
/// <summary>Queue that dequeues items in order of their priority</summary>
public class PriorityQueue<ItemType> : ICollection, IEnumerable<ItemType> {
public static PriorityQueue Syncronized(PriorityQueue<ItemType> P) {
return new PriorityQueue<ItemType>(ArrayList.Synchronized(P.InnerList), P.Comparer, false);
}
public static PriorityQueue<ItemType> ReadOnly(PriorityQueue<ItemType> P) {
return new PriorityQueue(ArrayList.ReadOnly(P.InnerList), P.Comparer, false);
}
public PriorityQueue()
: this(Comparer.Default) { }
public PriorityQueue(int C)
: this(Comparer.Default, C) { }
public PriorityQueue(IComparer c) {
Comparer = c;
}
public PriorityQueue(IComparer c, int Capacity) {
Comparer = c;
InnerList.Capacity = Capacity;
}
protected PriorityQueue(ArrayList Core, IComparer Comp, bool Copy) {
if(Copy)
InnerList = Core.Clone() as ArrayList;
else
InnerList = Core;
Comparer = Comp;
}
protected void SwitchElements(int i, int j) {
object h = InnerList[i];
InnerList[i] = InnerList[j];
InnerList[j] = h;
}
protected virtual int OnCompare(int i, int j) {
return Comparer.Compare(InnerList[i], InnerList[j]);
}
#region public methods
/// <summary>
/// Push an object onto the PQ
/// </summary>
/// <param name="O">The new object</param>
/// <returns>
/// The index in the list where the object is _now_. This will change when
/// objects are taken from or put onto the PQ.
/// </returns>
public int Queue(object O) {
int p = InnerList.Count, p2;
InnerList.Add(O); // E[p] = O
do {
if(p == 0)
break;
p2 = (p - 1) / 2;
if(OnCompare(p, p2) < 0) {
SwitchElements(p, p2);
p = p2;
} else
break;
} while(true);
return p;
}
/// <summary>
/// Get the smallest object and remove it.
/// </summary>
/// <returns>The smallest object</returns>
public object Dequeue() {
object result = InnerList[0];
int p = 0, p1, p2, pn;
InnerList[0] = InnerList[InnerList.Count - 1];
InnerList.RemoveAt(InnerList.Count - 1);
do {
pn = p;
p1 = 2 * p + 1;
p2 = 2 * p + 2;
if(InnerList.Count > p1 && OnCompare(p, p1) > 0) // links kleiner
p = p1;
if(InnerList.Count > p2 && OnCompare(p, p2) > 0) // rechts noch kleiner
p = p2;
if(p == pn)
break;
SwitchElements(p, pn);
} while(true);
return result;
}
/// <summary>
/// Notify the PQ that the object at position i has changed
/// and the PQ needs to restore order.
/// Since you dont have access to any indexes (except by using the
/// explicit IList.this) you should not call this function without knowing exactly
/// what you do.
/// </summary>
/// <param name="i">The index of the changed object.</param>
public void Update(int i) {
int p = i, pn;
int p1, p2;
do // aufsteigen
{
if(p == 0)
break;
p2 = (p - 1) / 2;
if(OnCompare(p, p2) < 0) {
SwitchElements(p, p2);
p = p2;
} else
break;
} while(true);
if(p < i)
return;
do // absteigen
{
pn = p;
p1 = 2 * p + 1;
p2 = 2 * p + 2;
if(InnerList.Count > p1 && OnCompare(p, p1) > 0) // links kleiner
p = p1;
if(InnerList.Count > p2 && OnCompare(p, p2) > 0) // rechts noch kleiner
p = p2;
if(p == pn)
break;
SwitchElements(p, pn);
} while(true);
}
/// <summary>
/// Get the smallest object without removing it.
/// </summary>
/// <returns>The smallest object</returns>
public object Peek() {
if(InnerList.Count > 0)
return InnerList[0];
return null;
}
public bool Contains(object value) {
return InnerList.Contains(value);
}
public void Clear() {
InnerList.Clear();
}
public int Count {
get {
return InnerList.Count;
}
}
IEnumerator IEnumerable.GetEnumerator() {
return InnerList.GetEnumerator();
}
public void CopyTo(Array array, int index) {
InnerList.CopyTo(array, index);
}
public object Clone() {
return new PriorityQueue(InnerList, Comparer, true);
}
public bool IsSynchronized {
get {
return InnerList.IsSynchronized;
}
}
public object SyncRoot {
get {
return this;
}
}
#endregion
#region explicit implementation
bool IList.IsReadOnly {
get {
return false;
}
}
object IList.this[int index] {
get {
return InnerList[index];
}
set {
InnerList[index] = value;
Update(index);
}
}
int IList.Add(object o) {
return Queue(o);
}
void IList.RemoveAt(int index) {
throw new NotSupportedException();
}
void IList.Insert(int index, object value) {
throw new NotSupportedException();
}
void IList.Remove(object value) {
throw new NotSupportedException();
}
int IList.IndexOf(object value) {
throw new NotSupportedException();
}
bool IList.IsFixedSize {
get {
return false;
}
}
#endregion
protected ArrayList InnerList = new ArrayList();
protected IComparer Comparer;
}
*/
} // namespace Nuclex.Support.Collections

View File

@ -0,0 +1,139 @@
using System;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the ring buffer class</summary>
[TestFixture]
public class RingMemoryStreamTest {
/// <summary>Prepares some test data for the units test methods</summary>
[TestFixtureSetUp]
public void Setup() {
this.testBytes = new byte[20];
for(int i = 0; i < 20; ++i)
this.testBytes[i] = (byte)i;
}
/// <summary>
/// Ensures that the ring buffer blocks write attempts that would exceed its capacity
/// </summary>
[Test, ExpectedException(typeof(OverflowException))]
public void TestTooLargeChunk() {
new RingMemoryStream(10).Write(this.testBytes, 0, 11);
}
/// <summary>
/// Ensures that the ring buffer still accepts write attempts what would fill the
/// entire buffer in one go.
/// </summary>
[Test]
public void TestBarelyFittingChunk() {
new RingMemoryStream(10).Write(this.testBytes, 0, 10);
}
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary>
[Test]
public void TestSplitBlockRead() {
RingMemoryStream rms = new RingMemoryStream(10);
rms.Write(this.testBytes, 0, 10);
rms.Read(this.testBytes, 0, 5);
rms.Write(this.testBytes, 0, 5);
byte[] actual = new byte[10];
rms.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 }, actual);
}
/// <summary>
/// Tests whether the ring buffer correctly returns partial data if more
/// data is requested than is contained in it.
/// </summary>
[Test]
public void TestEndOfStream() {
byte[] temp = new byte[10];
RingMemoryStream rms = new RingMemoryStream(10);
Assert.AreEqual(0, rms.Read(temp, 0, 5));
rms.Write(this.testBytes, 0, 5);
Assert.AreEqual(5, rms.Read(temp, 0, 10));
rms.Write(this.testBytes, 0, 6);
rms.Read(temp, 0, 5);
rms.Write(this.testBytes, 0, 9);
Assert.AreEqual(10, rms.Read(temp, 0, 20));
}
/// <summary>
/// Validates that the ring buffer can extend its capacity without loosing data
/// </summary>
[Test]
public void TestCapacityIncrease() {
RingMemoryStream rms = new RingMemoryStream(10);
rms.Write(this.testBytes, 0, 10);
rms.Capacity = 20;
byte[] actual = new byte[10];
rms.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
}
/// <summary>
/// Validates that the ring buffer can reduce its capacity without loosing data
/// </summary>
[Test]
public void TestCapacityDecrease() {
RingMemoryStream rms = new RingMemoryStream(20);
rms.Write(this.testBytes, 0, 10);
rms.Capacity = 10;
byte[] actual = new byte[10];
rms.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
}
/// <summary>
/// Checks that an exception is thrown when the ring buffer's capacity is
/// reduced so much it would have to give up some of its contained data
/// </summary>
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void TestCapacityDecreaseException() {
RingMemoryStream rms = new RingMemoryStream(20);
rms.Write(this.testBytes, 0, 20);
rms.Capacity = 10;
}
/// <summary>
/// Tests whether the auto reset feature works (resets the buffer point to the
/// left end of the buffer when it gets empty; mainly a performance feature).
/// </summary>
[Test]
public void TestAutoReset() {
RingMemoryStream rms = new RingMemoryStream(10);
byte[] temp = new byte[10];
rms.Write(this.testBytes, 0, 8);
rms.Read(temp, 0, 2);
rms.Read(temp, 0, 2);
rms.Read(temp, 0, 1);
rms.Read(temp, 0, 1);
Assert.AreEqual(2, rms.Length);
}
/// <summary>Test data for the ring buffer unit tests</summary>
private byte[] testBytes;
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View File

@ -0,0 +1,215 @@
using System;
using System.IO;
namespace Nuclex.Support.Collections {
/// <summary>Specialized memory stream for ring buffers</summary>
public class RingMemoryStream : Stream {
/// <summary>Initializes a new ring memory stream</summary>
/// <param name="capacity">Maximum capacity of the stream</param>
public RingMemoryStream(int capacity) {
this.ringBuffer = new MemoryStream(capacity);
this.ringBuffer.SetLength(capacity);
this.empty = true;
}
/// <summary>Maximum amount of data that will fit into the ring memory stream</summary>
public long Capacity {
get { return this.ringBuffer.Length; }
set {
int length = (int)Length;
if(value < length)
throw new ArgumentOutOfRangeException(
"New capacity is less than the stream's length"
);
// This could be done in a more efficient manner than just replacing
// the stream, but this operation will probably be called only once
// during the lifetime of the application -- if at all...
MemoryStream newBuffer = new MemoryStream((int)value);
newBuffer.SetLength(value);
if(length > 0)
Read(newBuffer.GetBuffer(), 0, length);
this.ringBuffer.Close();
this.ringBuffer = newBuffer;
this.startIndex = 0;
this.endIndex = length;
}
}
/// <summary>Whether it's possible to read from this stream</summary>
public override bool CanRead { get { return true; } }
/// <summary>Whether this stream supports random access</summary>
public override bool CanSeek { get { return false; } }
/// <summary>Whether it's possible to write into this stream</summary>
public override bool CanWrite { get { return true; } }
/// <summary>Flushes the buffers and writes down unsaved data</summary>
public override void Flush() { }
/// <summary>Current length of the stream</summary>
public override long Length {
get {
if((this.endIndex > this.startIndex) || this.empty) {
return this.endIndex - this.startIndex;
} else {
return this.ringBuffer.Length - this.startIndex + this.endIndex;
}
}
}
/// <summary>Current cursor position within the stream</summary>
public override long Position {
get { throw new NotSupportedException("The ring buffer does not support seeking"); }
set { throw new NotSupportedException("The ring buffer does not support seeking"); }
}
/// <summary>Reads data from the beginning of the stream</summary>
/// <param name="buffer">Buffer in which to store the data</param>
/// <param name="offset">Starting index at which to begin writing the buffer</param>
/// <param name="count">Number of bytes to read from the stream</param>
/// <returns>Die Number of bytes actually read</returns>
public override int Read(byte[] buffer, int offset, int count) {
// The end index lies behind the start index (usual case), so the
// ring memory is not fragmented. Example: |-----<#######>-----|
if((this.startIndex < this.endIndex) || this.empty) {
// The Stream interface requires us to return less than the requested
// number of bytes if we don't have enough data
count = Math.Min(count, this.endIndex - this.startIndex);
if(count > 0) {
this.ringBuffer.Position = this.startIndex;
this.ringBuffer.Read(buffer, offset, count);
this.startIndex += count;
if(this.startIndex == this.endIndex)
setEmpty();
}
// If the end index lies before the start index, the data in the
// ring memory stream is fragmented. Example: |#####>-------<#####|
} else {
int linearAvailable = (int)(this.ringBuffer.Length - this.startIndex);
// Will this read process cross the end of the ring buffer, requiring us to
// read the data in 2 steps?
if(count > linearAvailable) {
// The Stream interface requires us to return less than the requested
// number of bytes if we don't have enough data
count = Math.Min(count, linearAvailable + this.endIndex);
this.ringBuffer.Position = this.startIndex;
this.ringBuffer.Read(buffer, offset, linearAvailable);
this.ringBuffer.Position = 0;
this.startIndex = count - linearAvailable;
this.ringBuffer.Read(buffer, offset + linearAvailable, this.startIndex);
// Nope, the amount of requested data can be read in one piece without
// crossing the end of the ring buffer
} else {
this.ringBuffer.Position = this.startIndex;
this.ringBuffer.Read(buffer, offset, count);
this.startIndex += count;
}
if(this.startIndex == this.endIndex)
setEmpty();
}
return count;
}
/// <summary>Appends data to the end of the stream</summary>
/// <param name="buffer">Buffer containing the data to append</param>
/// <param name="offset">Starting index of the data in the buffer</param>
/// <param name="count">Number of bytes to write to the stream</param>
public override void Write(byte[] buffer, int offset, int count) {
// The end index lies behind the start index (usual case), so the
// ring memory is not fragmented. Example: |-----<#######>-----|
if((this.startIndex < this.endIndex) || this.empty) {
int linearAvailable = (int)(this.ringBuffer.Length - this.endIndex);
// If the data to be written would cross the ring memory stream's end,
// we have to check that there's enough space at the beginning of the
// stream to contain the remainder of the data.
if(count > linearAvailable) {
if(count > (linearAvailable + this.startIndex))
throw new OverflowException("Data does not fit in Ringbuffer");
this.ringBuffer.Position = this.endIndex;
this.ringBuffer.Write(buffer, offset, linearAvailable);
this.ringBuffer.Position = 0;
this.endIndex = count - linearAvailable;
this.ringBuffer.Write(buffer, offset + linearAvailable, this.endIndex);
// All data can be appended at the current stream position without
// crossing the ring memory stream's end
} else {
this.ringBuffer.Position = this.endIndex;
this.ringBuffer.Write(buffer, offset, count);
this.endIndex += count;
}
this.empty = false;
// If the end index lies before the start index, the ring memory stream
// has been fragmented. Hence, this means the gap into which we are about
// to write cannot be fragmented. Example: |#####>-------<#####|
} else {
if(count > (this.startIndex - this.endIndex))
throw new OverflowException("Data does not fit in Ringbuffer");
// Because the gap isn't fragmented, we can be sure that a single
// write call will suffice.
this.ringBuffer.Position = this.endIndex;
this.ringBuffer.Write(buffer, offset, count);
this.endIndex += count;
}
}
/// <summary>Jumps to the specified location within the stream</summary>
/// <param name="offset">Position to jump to</param>
/// <param name="origin">Origin towards which to interpret the offset</param>
/// <returns>The new offset within the stream</returns>
public override long Seek(long offset, SeekOrigin origin) {
throw new NotSupportedException("The ring buffer does not support seeking");
}
/// <summary>Changes the length of the stream</summary>
/// <param name="value">New length to resize the stream to</param>
public override void SetLength(long value) {
throw new NotSupportedException("This operation is not supported");
}
/// <summary>Resets the stream to its empty state</summary>
private void setEmpty() {
this.empty = true;
this.startIndex = 0;
this.endIndex = 0;
}
/// <summary>Internal stream containing the ring buffer data</summary>
private MemoryStream ringBuffer;
/// <summary>Start index of the data within the ring buffer</summary>
private int startIndex;
/// <summary>End index of the data within the ring buffer</summary>
private int endIndex;
/// <summary>Whether the ring buffer is empty</summary>
/// <remarks>
/// This field is required to differentiate between the ring buffer being
/// filled to the limit and being totally empty in the case that
/// the start index and the end index are the same.
/// </remarks>
bool empty;
}
} // namespace Nuclex.Support.Collections

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.IO;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Serialization {
/// <summary>Ensures that the binary serializer is working correctly</summary>
[TestFixture]
public class BinarySerializerTest {
private class TestSerializable : IBinarySerializable {
public void Load(BinaryReader reader) { this.Dummy = reader.ReadInt32(); }
public void Save(BinaryWriter writer) { writer.Write(this.Dummy); }
public int Dummy;
}
/// <summary>Prepares some test data for the units test methods</summary>
[TestFixtureSetUp]
public void Setup() {
}
/// <summary>
/// Ensures that the ring buffer blocks write attempts that would exceed its capacity
/// </summary>
[Test]
public void TestTooLargeChunk() {
MemoryStream buffer = new MemoryStream();
// Fill and save
{
List<TestSerializable> serializables = new List<TestSerializable>();
serializables.Add(new TestSerializable());
serializables.Add(new TestSerializable());
serializables[0].Dummy = 123;
serializables[1].Dummy = 456;
BinarySerializer<TestSerializable>.SaveCollection(
serializables, new BinaryWriter(buffer)
);
buffer.Position = 0;
}
// Load and validate
{
List<TestSerializable> serializables = new List<TestSerializable>();
BinarySerializer<TestSerializable>.LoadCollection(
serializables, new BinaryReader(buffer)
);
Assert.AreEqual(2, serializables.Count);
Assert.AreEqual(123, serializables[0].Dummy);
Assert.AreEqual(456, serializables[1].Dummy);
}
}
}
} // namespace Nuclex.Support.Serialization
#endif // UNITTEST

View File

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Nuclex.Support.Serialization;
namespace Nuclex.Support.Serialization {
/// <summary>Utility class for serialization objects into binary data</summary>
/// <typeparam name="BinarySerializableType">Data type to be serialized</typeparam>
public static class BinarySerializer<BinarySerializableType>
where BinarySerializableType : IBinarySerializable {
/// <summary>Serializes a collection of binary serializable objects</summary>
/// <param name="collection">Collection to be serialized</param>
/// <param name="writer">BinaryWriter to serialize the collection into</param>
public static void SaveCollection(
ICollection<BinarySerializableType> collection, BinaryWriter writer
) {
// Save the file format version so the loading routine can detect
// which version of the file format has to be loaded
writer.Write((int)1);
// Serialize all the blueprints in the collection
writer.Write((int)collection.Count);
foreach(BinarySerializableType item in collection) {
// Save the type name of the object so we can recreate it later
writer.Write(item.GetType().AssemblyQualifiedName);
// Let the object save its own data
((IBinarySerializable)item).Save(writer);
} // foreach
}
/// <summary>Loads a collection from its serialized representation</summary>
/// <param name="collection">Collection to be deserialized into</param>
/// <param name="reader">Reader to use for reading the collection</param>
public static void LoadCollection(
ICollection<BinarySerializableType> collection, BinaryReader reader
) {
// Read and verify the version of the file format this was saved in
int version = reader.ReadInt32();
if(version > 1)
throw new InvalidOperationException("File format is too new");
// Read all the serialized blueprints
int count = reader.ReadInt32();
for(int index = 0; index < count; ++index) {
// Try to create an instance from the serialized type name
BinarySerializableType item = (BinarySerializableType)Activator.CreateInstance(
Type.GetType(reader.ReadString())
);
// Let the blueprint load its own data and add it to the collection
((IBinarySerializable)item).Load(reader);
collection.Add(item);
} // for
}
} // class BinarySerializer
} // namespace Nuclex.Support.Serialization

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Nuclex.Support.Serialization {
/// <summary>Interface for objects able to serialize themselfes into a binary format</summary>
/// <remarks>
/// Sometimes, the limitations of XML serialization are too strict, especially
/// in the context of a game where you might need to serialize larger chunks of
/// binary data or in cases where you do not wish to expose a default constructor
/// in your classes. This interface defines two simple methods that can be
/// used to load and save an object's state in a simple manner.
/// </remarks>
public interface IBinarySerializable {
/// <summary>Loads the object's state from its serialized representation</summary>
/// <param name="reader">Reader to use for reading the object's state</param>
void Load(BinaryReader reader);
/// <summary>Save the object's state into a serialized representation</summary>
/// <param name="writer">Writer to use for writing the object's state</param>
void Save(BinaryWriter writer);
}
} // namespace Nuclex.Support.Serialization