From 237fb57fc808792c236c44802cde6c28fa1ae350 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Thu, 11 Feb 2010 21:07:04 +0000 Subject: [PATCH] Added an interface for the scheduler service (this might be once candidate for replacement by different implementations); added a new helper class for the StringBuilder that allows garbage-free appending of integers and floats; added unit tests for most of the code git-svn-id: file:///srv/devel/repo-conversion/nusu@188 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Support (Xbox 360).csproj | 5 + Nuclex.Support.csproj | 5 + Source/FloatHelper.Test.cs | 4 +- Source/Scheduling/ISchedulerService.cs | 101 +++++++++++++ Source/Scheduling/Scheduler.cs | 2 +- Source/StringBuilderHelper.Test.cs | 151 ++++++++++++++++++ Source/StringBuilderHelper.cs | 202 +++++++++++++++++++++++++ Source/StringHelper.cs | 1 + 8 files changed, 468 insertions(+), 3 deletions(-) create mode 100644 Source/Scheduling/ISchedulerService.cs create mode 100644 Source/StringBuilderHelper.Test.cs create mode 100644 Source/StringBuilderHelper.cs 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 +