diff --git a/Nuclex.Support (Xbox 360).csproj b/Nuclex.Support (Xbox 360).csproj
index 987b411..4e4bdd8 100644
--- a/Nuclex.Support (Xbox 360).csproj
+++ b/Nuclex.Support (Xbox 360).csproj
@@ -145,6 +145,7 @@
GenericTimeSource.cs
+
@@ -305,6 +306,10 @@
ChainStream.cs
+
+
+ StringBuilderHelper.cs
+
StringHelper.cs
diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj
index a2affe1..42dffe0 100644
--- a/Nuclex.Support.csproj
+++ b/Nuclex.Support.csproj
@@ -131,6 +131,7 @@
GenericTimeSource.cs
+
@@ -291,6 +292,10 @@
ChainStream.cs
+
+
+ StringBuilderHelper.cs
+
StringHelper.cs
diff --git a/Source/FloatHelper.Test.cs b/Source/FloatHelper.Test.cs
index 5151231..09732ac 100644
--- a/Source/FloatHelper.Test.cs
+++ b/Source/FloatHelper.Test.cs
@@ -18,11 +18,11 @@ License along with this library
*/
#endregion
+#if UNITTEST
+
using System;
using System.Collections.Generic;
-#if UNITTEST
-
using NUnit.Framework;
namespace Nuclex.Support {
diff --git a/Source/Scheduling/ISchedulerService.cs b/Source/Scheduling/ISchedulerService.cs
new file mode 100644
index 0000000..7df7e9f
--- /dev/null
+++ b/Source/Scheduling/ISchedulerService.cs
@@ -0,0 +1,101 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2009 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Nuclex.Support.Scheduling {
+
+ /// Service that allows the scheduled invocation of tasks
+ public interface ISchedulerService {
+
+ /// Schedules a notification at the specified absolute time
+ ///
+ /// Absolute time at which the notification will occur
+ ///
+ ///
+ /// Callback that will be invoked when the notification is due
+ ///
+ /// A handle that can be used to cancel the notification
+ ///
+ /// The notification is scheduled for the indicated absolute time. If the system
+ /// enters/leaves daylight saving time or the date/time is changed (for example
+ /// when the system synchronizes with an NTP server), this will affect
+ /// the notification. So if you need to be notified after a fixed time, use
+ /// the NotifyIn() method instead.
+ ///
+ object NotifyAt(DateTime notificationTime, WaitCallback callback);
+
+ ///
+ /// Schedules a recurring notification after the specified amount of milliseconds
+ ///
+ ///
+ /// Milliseconds after which the first notification will occur
+ ///
+ ///
+ /// Interval in milliseconds at which the notification will be repeated
+ ///
+ ///
+ /// Callback that will be invoked when the notification is due
+ ///
+ /// A handle that can be used to cancel the notification
+ object NotifyEach(int delayMilliseconds, int intervalMilliseconds, WaitCallback callback);
+
+ ///
+ /// Schedules a recurring notification after the specified time span
+ ///
+ /// Delay after which the first notification will occur
+ /// Interval at which the notification will be repeated
+ ///
+ /// Callback that will be invoked when the notification is due
+ ///
+ /// A handle that can be used to cancel the notification
+ object NotifyEach(TimeSpan delay, TimeSpan interval, WaitCallback callback);
+
+ ///
+ /// Schedules a notification after the specified amount of milliseconds
+ ///
+ ///
+ /// Number of milliseconds after which the notification will occur
+ ///
+ ///
+ /// Callback that will be invoked when the notification is due
+ ///
+ /// A handle that can be used to cancel the notification
+ object NotifyIn(int delayMilliseconds, WaitCallback callback);
+
+ /// Schedules a notification after the specified time span
+ /// Delay after which the notification will occur
+ ///
+ /// Callback that will be invoked when the notification is due
+ ///
+ /// A handle that can be used to cancel the notification
+ object NotifyIn(TimeSpan delay, WaitCallback callback);
+
+ /// Cancels a scheduled notification
+ ///
+ /// Handle of the notification that will be cancelled
+ ///
+ void Cancel(object notificationHandle);
+
+ }
+
+} // namespace Nuclex.Support.Scheduling
diff --git a/Source/Scheduling/Scheduler.cs b/Source/Scheduling/Scheduler.cs
index f0a5f26..2d73f68 100644
--- a/Source/Scheduling/Scheduler.cs
+++ b/Source/Scheduling/Scheduler.cs
@@ -28,7 +28,7 @@ using Nuclex.Support.Collections;
namespace Nuclex.Support.Scheduling {
/// Schedules actions for execution at a future point in time
- public class Scheduler : IDisposable {
+ public class Scheduler : ISchedulerService, IDisposable {
/// One tick is 100 ns, meaning 10000 ticks equal 1 ms
private const long TicksPerMillisecond = 10000;
diff --git a/Source/StringBuilderHelper.Test.cs b/Source/StringBuilderHelper.Test.cs
new file mode 100644
index 0000000..e2d4c35
--- /dev/null
+++ b/Source/StringBuilderHelper.Test.cs
@@ -0,0 +1,151 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2009 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+#if UNITTEST
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using NUnit.Framework;
+
+namespace Nuclex.Support {
+
+ ///
+ /// Unit test for the helper class to .NET's string builder
+ ///
+ [TestFixture]
+ public class StringBuilderHelperTest {
+
+ ///
+ /// Verifies that bytes are correctly appended to a string builder
+ ///
+ [Test]
+ public void TestAppendByte() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilderHelper.Append(builder, (byte)255);
+
+ Assert.AreEqual("255", builder.ToString());
+ }
+
+ ///
+ /// Verifies that a byte with value 0 is correctly appended to a string builder
+ ///
+ [Test]
+ public void TestAppendNullByte() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilderHelper.Append(builder, (byte)0);
+
+ Assert.AreEqual("0", builder.ToString());
+ }
+
+ ///
+ /// Verifies that a positive integer is correctly appended to a string builder
+ ///
+ [Test]
+ public void TestAppendPositiveInteger() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilderHelper.Append(builder, 12345);
+
+ Assert.AreEqual("12345", builder.ToString());
+ }
+
+ ///
+ /// Verifies that an integer with value 0 is correctly appended to a string builder
+ ///
+ [Test]
+ public void TestAppendNullInteger() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilderHelper.Append(builder, 0);
+
+ Assert.AreEqual("0", builder.ToString());
+ }
+
+ ///
+ /// Verifies that a negative integer is correctly appended to a string builder
+ ///
+ [Test]
+ public void TestAppendNegativeInteger() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilderHelper.Append(builder, -12345);
+
+ Assert.AreEqual("-12345", builder.ToString());
+ }
+
+ ///
+ /// Verifies that negative floating point values are correctly converted
+ ///
+ [Test]
+ public void TestAppendNegativeFloat() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilderHelper.Append(builder, -32.015625f);
+
+ Assert.AreEqual("-32.015625", builder.ToString());
+ }
+
+ ///
+ /// Verifies that positive floating point values are correctly converted
+ ///
+ [Test]
+ public void TestAppendPositiveFloat() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilderHelper.Append(builder, 10.0625f);
+
+ Assert.AreEqual("10.0625", builder.ToString());
+ }
+
+ ///
+ /// Verifies that very small floating point values are correctly converted
+ ///
+ [Test]
+ public void TestAppendSmallFloat() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilderHelper.Append(builder, 0.00390625f);
+
+ Assert.AreEqual("0.00390625", builder.ToString());
+ }
+
+ ///
+ /// Verifies that very large floating point values are correctly converted
+ ///
+ [Test]
+ public void TestAppendHugeFloat() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilderHelper.Append(builder, 1000000000.0f);
+
+ Assert.AreEqual("1000000000.0", builder.ToString());
+ }
+
+ ///
+ /// Verifies that the contents of a string builder can be cleared
+ ///
+ [Test]
+ public void TestClear() {
+ StringBuilder builder = new StringBuilder("Hello World");
+ StringBuilderHelper.Clear(builder);
+
+ Assert.AreEqual(string.Empty, builder.ToString());
+ }
+
+ }
+
+} // namespace Nuclex.Support
+
+#endif // UNITTEST
diff --git a/Source/StringBuilderHelper.cs b/Source/StringBuilderHelper.cs
new file mode 100644
index 0000000..9bac9d9
--- /dev/null
+++ b/Source/StringBuilderHelper.cs
@@ -0,0 +1,202 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2009 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+
+namespace Nuclex.Support {
+
+ /// Contains helper methods for the string builder class
+ public class StringBuilderHelper {
+
+ /// Predefined unicode characters for the numbers 0 to 9
+ private static readonly char[] numbers = new char[] {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
+ };
+
+ /// Clears the contents of a string builder
+ /// String builder that will be cleared
+ public static void Clear(StringBuilder builder) {
+ builder.Remove(0, builder.Length);
+ }
+
+ ///
+ /// Appends an integer to a string builder without generating garbage
+ ///
+ /// String builder to which an integer will be appended
+ /// Byte that will be appended to the string builder
+ ///
+ /// The normal StringBuilder.Append() method generates garbage when converting
+ /// integer arguments whereas this method will avoid any garbage, albeit probably
+ /// with a small performance impact compared to the built-in method.
+ ///
+ public static void Append(StringBuilder builder, byte value) {
+ recursiveAppend(builder, value);
+ }
+
+ ///
+ /// Appends an integer to a string builder without generating garbage
+ ///
+ /// String builder to which an integer will be appended
+ /// Integer that will be appended to the string builder
+ ///
+ /// The normal StringBuilder.Append() method generates garbage when converting
+ /// integer arguments whereas this method will avoid any garbage, albeit probably
+ /// with a small performance impact compared to the built-in method.
+ ///
+ public static void Append(StringBuilder builder, int value) {
+ if(value < 0) {
+ builder.Append('-');
+ recursiveAppend(builder, -value);
+ } else {
+ recursiveAppend(builder, value);
+ }
+ }
+
+ ///
+ /// Appends an long integer to a string builder without generating garbage
+ ///
+ /// String builder to which an integer will be appended
+ /// Long integer that will be appended to the string builder
+ ///
+ /// The normal StringBuilder.Append() method generates garbage when converting
+ /// integer arguments whereas this method will avoid any garbage, albeit probably
+ /// with a small performance impact compared to the built-in method.
+ ///
+ public static void Append(StringBuilder builder, long value) {
+ if(value < 0) {
+ builder.Append('-');
+ recursiveAppend(builder, -value);
+ } else {
+ recursiveAppend(builder, value);
+ }
+ }
+
+ ///
+ /// Appends a floating point value to a string builder without generating garbage
+ ///
+ /// String builder the value will be appended to
+ /// Value that will be appended to the string builder
+ public static void Append(StringBuilder builder, float value) {
+ const int ExponentBits = 0xFF; // Bit mask for the exponent bits
+ const int FractionalBitCount = 23; // Number of bits for fractional part
+ const int ExponentBias = 127; // Bias subtraced from exponent
+ const int NumericBitCount = 31; // Bits without sign
+
+ // You do not modify these as they're calculated based on the
+ const int FractionalBits = (2 << FractionalBitCount) - 1;
+ const int HighestFractionalBit = (1 << FractionalBitCount);
+ const int FractionalBitCountPlusOne = FractionalBitCount + 1;
+
+ int intValue = FloatHelper.ReinterpretAsInt(value);
+
+ int exponent = ((intValue >> FractionalBitCount) & ExponentBits) - ExponentBias;
+ int mantissa = (intValue & FractionalBits) | HighestFractionalBit;
+
+ int integral;
+ int fractional;
+ if(exponent >= 0) {
+ if(exponent >= FractionalBitCount) {
+ Debug.Assert(exponent < NumericBitCount);
+ integral = mantissa << (exponent - FractionalBitCount);
+ fractional = 0;
+ } else {
+ integral = mantissa >> (FractionalBitCount - exponent);
+ fractional = (mantissa << (exponent + 1)) & FractionalBits;
+ }
+ } else {
+ Debug.Assert(exponent >= -FractionalBitCount);
+ integral = 0;
+ fractional = (mantissa & FractionalBits) >> -(exponent + 1);
+ }
+/*
+ if(exponent >= NumericBitCount) {
+ throw new ArgumentException("Value too large", "value");
+ } else if(exponent < -FractionalBitCount) {
+ throw new ArgumentException("Value too small", "value");
+ } else if(exponent >= FractionalBitCount) {
+ integral = mantissa << (exponent - FractionalBitCount);
+ fractional = 0;
+ } else if(exponent >= 0) {
+ integral = mantissa >> (FractionalBitCount - exponent);
+ fractional = (mantissa << (exponent + 1)) & FractionalBits;
+ } else { // exp2 < 0
+ integral = 0;
+ fractional = (mantissa & FractionalBits) >> -(exponent + 1);
+ }
+*/
+ // Build the integral part
+ if(intValue < 0) {
+ builder.Append('-');
+ }
+ if(integral == 0) {
+ builder.Append('0');
+ } else {
+ recursiveAppend(builder, integral);
+ }
+
+ builder.Append('.');
+
+ // Build the fractional part
+ if(fractional == 0) {
+ builder.Append('0');
+ } else {
+ while(fractional != 0) {
+ fractional *= 10;
+ int digit = (fractional >> FractionalBitCountPlusOne);
+ builder.Append(numbers[digit]);
+ fractional &= FractionalBits;
+ }
+ }
+ }
+
+ /// Recursively appends a number's characters to a string builder
+ /// String builder the number will be appended to
+ /// Remaining digits that will be recursively processed
+ private static void recursiveAppend(StringBuilder builder, int remaining) {
+ int digit;
+ int tenth = Math.DivRem(remaining, 10, out digit);
+
+ if(tenth > 0) {
+ recursiveAppend(builder, tenth);
+ }
+
+ builder.Append(numbers[digit]);
+ }
+
+ /// Recursively appends a number's characters to a string builder
+ /// String builder the number will be appended to
+ /// Remaining digits that will be recursively processed
+ private static void recursiveAppend(StringBuilder builder, long remaining) {
+ long digit;
+ long tenth = Math.DivRem(remaining, 10, out digit);
+
+ if(tenth > 0) {
+ recursiveAppend(builder, tenth);
+ }
+
+ builder.Append(numbers[digit]);
+ }
+
+ }
+
+} // namespace Nuclex.Support
diff --git a/Source/StringHelper.cs b/Source/StringHelper.cs
index 8044bca..3cb8efb 100644
--- a/Source/StringHelper.cs
+++ b/Source/StringHelper.cs
@@ -165,3 +165,4 @@ namespace Nuclex.Support {
}
} // namespace Nuclex.Support
+