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:
commit
ce8df64be5
114
Nuclex.Support.csproj
Normal file
114
Nuclex.Support.csproj
Normal 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>
|
31
Properties/AssemblyInfo.cs
Normal file
31
Properties/AssemblyInfo.cs
Normal 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")]
|
38
Source/Collections/Parentable.cs
Normal file
38
Source/Collections/Parentable.cs
Normal 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
|
107
Source/Collections/ParentingCollection.cs
Normal file
107
Source/Collections/ParentingCollection.cs
Normal 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<>
|
||||||
|
/// 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
|
235
Source/Collections/PriorityQueue.cs
Normal file
235
Source/Collections/PriorityQueue.cs
Normal 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
|
139
Source/Collections/RingMemoryStream.Test.cs
Normal file
139
Source/Collections/RingMemoryStream.Test.cs
Normal 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
|
215
Source/Collections/RingMemoryStream.cs
Normal file
215
Source/Collections/RingMemoryStream.cs
Normal 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
|
66
Source/Serialization/BinarySerializer.Test.cs
Normal file
66
Source/Serialization/BinarySerializer.Test.cs
Normal 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
|
71
Source/Serialization/BinarySerializer.cs
Normal file
71
Source/Serialization/BinarySerializer.cs
Normal 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
|
27
Source/Serialization/IBinarySerializable.cs
Normal file
27
Source/Serialization/IBinarySerializable.cs
Normal 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
|
Loading…
Reference in New Issue
Block a user