Preface
The Murl Engine is a cross platform framework. All platform dependent implementations are abstracted to ensure the compatibility across different platforms.
Different platforms use different C++ compiler. These compilers differ slightly especially in the area of standard libraries and the STL (Standard Template Library).
Therefore you should avoid the usage of standard libraries like stdio.h
to avoid possible problems when compiling the application with different compilers. The Murl Engine already provides all necessary functions and templates and usually further libraries aren't needed. This means that you should never use standard #includes
in your application source code like e.g.:
The following sections show how to implement applications with the Murl Engine without using these standard libraries.
The best cross platform compatibility is ensured when only interfaces and functions of the Murl headers are used.
Of course it is also possible to use other headers/libraries, if in a particular case the framework does not provide a proper implementation for a special feature. In such a case feel free to create a new thread in our forums to discuss possible new features or corrections.
This tutorial is huge and contains the following subsections:
- NTL Container vs. STL Container
- Text Output
- String Class
- Array Class
- Index Class
- Queue Class
- Map Class
- Time and Date
- Sorting
- Mathematical Functions
- Data Class
- Pointer
- Utility Functions
NTL Container vs. STL Container
The probably most often used container classes in C++ are the classes of the Standard Template Library (STL).
Unfortunately these container implementations differ between different compilers especially in regard to the execution speed and in rare cases also in the behavior.
Due to this fact the Murl Engine provides adapted NTL container classes - see www.ultimatepp.org.
These container classes are very comfortable when you get used to them and provide a considerably faster and more reliable execution time compared to the STL container classes.
Mapping STL → Murl Container
To improve the performance the memory usage of the NTL is a little bit simpler compared to the STL and does not use the copy constructor for internal memory movements. However, the copy constructor is used when elements are added (e.g. Add()
, Insert()
).
In general, we distinguish between simple containers and object containers. The following simple containers should only be used with primitive data types (e.g. UInt32
, Real
, etc.) and simple structs:
Big structs and objects should always be stored in one of the following object containers:
Object containers internally use simple containers to store references to the instances of their elements. Therefore it is never necessary to move the instances itself in the memory. Also external references to these instances remain always valid, which is another advantage of object containers. A detailed example about the differences can be found in the subsection Simple Container vs. Object Container.
Of course, the copy constructor is used when elements are copied or added to a container - regardless if a simple container or an object container is used. Only for internal memory movements the usage of copy constructors are avoided.
Efficient container classes are very important to achieve a good App performance. This is even more important for multimedia applications on mobile devices.
Text Output
The following sections cover mainly the usage of container classes. The results are simply shown as text output messages in the console window.
All listed source code fragments for this tutorial can be found in the file container_logic.cpp
. The following header is used for the text output:
#include "murl_system_console.h"
The text output examples are implemented in the following method:
void App::ContainerLogic::PrintDemo()
The following subsections show different possibilities to generate text output with the Murl Engine:
Print a Simple Text
System::Console::Print("Hello World!\n");
Depending on the platform the text output can be seen in:
- iOS/OSX: Xcode debug console
- Windows: console window of the application
- Android: LogCat message with the priority
ANDROID_LOG_INFO
Debug Messages
The header murl_debug_trace.h
contains methods to output debug messages. Debug messages are not printed in release builds.
MURL_TRACE(0, "Debug Hello World");
Prints the following text:
The first parameter (0
) defines the "log level" for this message. Debug messages are suppressed, if the log level is higher than the global log level. If e.g. the global log level has been set to 1 only debug messages with a log level smaller or equal to 1 are printed. The global log level can be set with the method Debug::Logger::SetLogLevel
. The method Debug::Logger::GetLogLevel
can be used to read the global log level.
It is remarkable that also the method name and the line number are printed in front of the debug message.
The following alternative method can be used, if only the debug message should be printed:
Debug::Trace("Debug Hello World");
Prints the following text:
Debug messages on Android are printed as LogCat messages with the priority ANDROID_LOG_DEBUG
.
Erro Messages
The header murl_debug_error.h
contains methods to output error messages. Error messages are also printed in release builds.
MURL_ERROR("Error Hello World");
Prints the following text:
Again, it is remarkable that also the method name and the line number are printed in front of the error message.
The following alternative method can be used, if only the message should be printed:
Debug::Error("Error Hello World");
Prints the following text:
Error messages on Android are printed as LogCat messages with the priority ANDROID_LOG_ERROR
.
Configuration
The methods IAppConfiguration::SetDebugOutputFunctionVerbosity()
and IAppConfiguration::SetDebugTraceLevel()
can be used to configure the MURL_TRACE and the MURL_ERROR message output.
Variables
All methods mentioned above (Console/Debug/Error) are of course also supporting the usage of variables like the well-known printf
function.
UInt32 what = 42; System::Console::Print("Hello World %d!\n", what);
Prints the following text:
Line Break
Debug and Error methods automatically add a line break to every message while the System::Console::Print
method prints exactly the given text without additional line break. The character \n
can be used to create a line break manually. Alternatively the PrintEndline()
method can be used but be aware that this method does not support the usage of variables.
String Class
The C++ programming language does not provide a proper character string class. To compensate for that the class String
declared in the header murl_string.h
is provided.
The String
examples are implemented in the following method:
void App::ContainerLogic::StringDemo()
String Class
A String
object can easily be instantiated wherever needed:
String myText;
Such a instantiated String
is initially empty (IsEmpty()
):
if (myText.IsEmpty()) { System::Console::Print("Text is Empty\n"); }
Contrary to STL Strings, a NTL String always maintains a valid pointer to its character array. The method Begin()
can be used to get this pointer (equivalent to c_str()
from STL String).
System::Console::Print("myText contains '%s'\n", myText.Begin());
Assign Strings (=
):
myText = "Hello"; System::Console::Print("myText contains '%s'\n", myText.Begin());
Append Strings (+=
):
myText += " World!"; System::Console::Print("myText contains '%s'\n", myText.Begin());
Clear Strings (Clear()
):
myText.Clear(); System::Console::Print("myText contains '%s'\n", myText.Begin());
- Note
- Caution! The method
.Begin()
have always to be used when printing the content of aString
object with%s
. This method returns a pointer to a valid null-terminated character array. Omitting the.Begin()
part causes error messages or erroneous output depending on the individual platform. Example:Prints the following text in Visual Studio (Xcode shows an error message):String valString = "Value = "; UInt32 val = 42; Debug::Trace("%s %d", valString, val); Debug::Trace("%s %d", valString.Begin(), val);
In the first case theValue = 8Value = 42String
object is put onto the stack instead of the null-terminated character array but the print method is expecting a standard C string followed by a integer value which is in the end causing the erroneous output. In the second case the output is correct.
String Manipulation
Initialize strings (String()
, =
):
String murlText("Murl"); String engineText = "Engine";
Concatenate strings (+
):
System::Console::Print(murlText + "-" + engineText + "\n");
Create a character sequence (String()
):
Note that single quotes need to be used because this method requires a single character.
String text('X', 4); // text contains now: "XXXX" System::Console::Print(text + "\n");
Concatenate characters (Cat()
):
Note that single quotes need to be used because this method requires a single character.
text.Cat('Y', 5); // text contains now: "XXXXYYYYY" System::Console::Print(text + "\n");
Set characters (Set()
):
Note that single quotes need to be used because this method requires a single character.
text.Set(7, 'Z'); text.Set(8, 'Z'); // text contains now: "XXXXYYYZZ" System::Console::Print(text + "\n");
Insert characters or strings (Insert()
):
text.Insert(0, 'A'); // text contains now: "AXXXXYYYZZ" System::Console::Print(text + "\n"); text.Insert(2, "BB"); // text contains now: "AXBBXXXYYYZZ" System::Console::Print(text + "\n");
Remove characters (Remove()
):
text.Remove(5, 2); // text contains now: "AXBBXYYYZZ" System::Console::Print(text + "\n");
Replace substrings (Replace()
):
All substrings matching with "YY"
are replaced with the replace string "CCC".
text.Replace("YY", "CCC"); // text contains now: "AXBBXCCCYZZ" System::Console::Print(text + "\n");
Substring Operations
Remove leading and/or trailing whitespace characters (Trim()
, TrimLeft()
, TrimRight()
):
Note that the method returns a new string (no modification of the source string).
String whiteSpace(" white "); System::Console::Print("'" + whiteSpace.Trim() + "'\n"); // prints: 'white' System::Console::Print("'" + whiteSpace.TrimLeft() + "'\n"); // prints: 'white ' System::Console::Print("'" + whiteSpace.TrimRight() + "'\n"); // prints: ' white'
Create substrings (Left()
, Right()
, Mid()
):
Note that the method returns a new string (no modification of the source string).
String someText("Spraylight"); System::Console::Print(someText.Left(5) + "\n"); // prints: Spray System::Console::Print(someText.Right(5) + "\n"); // prints: light System::Console::Print(someText.Mid(2, 6) + "\n"); // prints: raylig
String length (number of bytes) (GetLength()
):
Note that the method (GetLength()
) returns the number of bytes. The number of UTF-8 characters may differ and can be obtained with the method (GetLengthUTF8()
).
someText += "ray"; System::Console::Print("Text: %s, Length: %d", someText.Begin(), someText.GetLength()); // prints: Text: Spraylightray, Length: 13
Find substrings (Find()
, ReverseFind()
):
The method returns the index of the substring in the range of 0 .. (GetLength() – 1)
or a negative number if the substring does not occur. Optionally the search can be started from a given index in the valid range.
System::Console::Print("Position: %d\n", someText.Find("what")); System::Console::Print("Position: %d\n", someText.Find("ray", 1)); System::Console::Print("Position: %d\n", someText.Find("ray", 3)); System::Console::Print("Position: %d\n", someText.ReverseFind("ray"));
Find the first occurrence of a character from a pool of characters (FindFirstOf()
,FindFirstNotOf()
):
System::Console::Print("Position: %d\n", someText.FindFirstOf("igl")); System::Console::Print("Position: %d\n", someText.FindFirstNotOf("prS"));
Verify the start/end of a string (StartsWith()
, EndsWith()
):
System::Console::Print("Check: %s\n", someText.StartsWith("who") ? "yes" : "no"); System::Console::Print("Check: %s\n", someText.StartsWith("Spra") ? "yes" : "no"); System::Console::Print("Check: %s\n", someText.EndsWith("tray") ? "yes" : "no");
Compare strings (==
, !=
, >
, <
):
System::Console::Print("Check: %s\n", someText == "Spray" ? "yes" : "no"); System::Console::Print("Check: %s\n", someText != "ray" ? "yes" : "no"); System::Console::Print("Check: %s\n", someText == "Spraylightray" ? "yes" : "no"); System::Console::Print("Check: %s\n", someText > "Spray" ? "yes" : "no"); System::Console::Print("Check: %s\n", someText < "light" ? "yes" : "no");
UTF-8 Strings
Basically the whole Murl Engine framework is solely working with UTF-8 strings. The String class provides some special methods to handle UTF-8 strings.
Make sure to set the standard encoding of the editor to UTF-8 in order to be able to enter UTF-8 strings directly in the source code!
Note that unfortunately the output of special UTF-8 characters does not work properly in the Windows console (wrong codepage, font).
String utf8String("Dürüm Döner"); System::Console::PrintEndline(utf8String); utf8String += "® €2,49 𝄞"; System::Console::PrintEndline(utf8String);
Lowercase and uppercase conversion (ToLowerUTF8()
, ToUpperUTF8()
):
Note that the method returns a new string (no modification of the source string).
System::Console::PrintEndline(utf8String.ToLowerUTF8()); System::Console::PrintEndline(utf8String.ToUpperUTF8());
UTF-8 length vs. byte length (GetLength()
, GetLengthUTF8()
):
The number of bytes for some UTF-8 characters is bigger than 1. Therefore the number of bytes in a UTF-8 string is not necessarily the same as the number of UTF-8 characters!
System::Console::Print("Bytes %d != %d Chars", utf8String.GetLength(), utf8String.GetLengthUTF8());
Modifying UTF-8 characters (GetUTF8Chars()
):
A String
can be converted into a StringArray
to allow the manipulation of individual UTF-8 characters. Each UTF-8 character is stored as a separate String element in the Array.
StringArray utf8Chars; utf8Chars = utf8String.GetUTF8Chars();
The individual characters can explicitly be used as Strings:
for (UInt32 i = 0; i < utf8Chars.GetCount(); i++) { System::Console::Print(utf8Chars[i] + " "); } System::Console::Print("\n");
The StringArrays
can be used to modify individual characters, e.g. replacing the Euro character with a Dollar character:
for (UInt32 i = 0; i < utf8Chars.GetCount(); i++) { if (utf8Chars[i] == "€") { utf8Chars[i] = "$"; } }
The method Cat()
can be used to create a String
object from a StringArray
:
String newUtf8; newUtf8.Cat(utf8Chars); System::Console::PrintEndline(newUtf8);
Further String Methods
Most of the shown String
methods also exist with additional parameter sets. A complete list of all methods can be found in the String API reference
or directly in the header file murl_string.h
.
String Conversion
The header file murl_util_string_conversion.h
provides several string conversion functions. A detailed description for all methods can be found in the API reference on the String Conversion Functions
page.
Convert strings into numbers:
Util::StringToBool()
Util::StringToUInt64()
Util::StringToSInt64()
Util::StringToUInt32()
Util::StringToSInt32()
Util::StringToDouble()
Util::StringToFloat()
Util::StringToColor()
Util::StringToColorComponent()
Util::HexStringToUInt64()
Util::HexStringToUInt32()
Util::AngleStringToDouble()
Example:
String numString = "12345.678"; Double doubleNumber; if (Util::StringToDouble(numString, doubleNumber)) { System::Console::Print("Double Number %f\n", doubleNumber); } SInt32 intNumber; if (Util::StringToSInt32(numString, intNumber)) { System::Console::Print("SInt32 Number %d\n", intNumber); } if (!Util::StringToDouble("not a number", doubleNumber)) { System::Console::PrintEndline("Not a number!"); }
Array Conversion:
Util::StringToBoolArray()
Util::StringToUInt64Array()
Util::StringToSInt64Array()
Util::StringToUInt32Array()
Util::StringToSInt32Array()
Util::StringToDoubleArray()
Util::StringToFloatArray()
Example:
String someNumbers = "17;08;32;42;15;26"; UInt32Array numberArray; Util::StringToUInt32Array(someNumbers, ';', numberArray); for (UInt32 i = 0; i < numberArray.GetCount(); i++) { System::Console::Print("%d ", numberArray[i]); } System::Console::Print("\n");
Convert numbers into strings:
Util::UInt64ToString()
Util::SInt64ToString()
Util::UInt32ToString()
Util::SInt32ToString()
Util::DoubleToString()
Util::BoolToString()
Example:
UInt64 uint64Value = 123456789; String uint64String = Util::UInt64ToString(uint64Value); System::Console::PrintEndline(uint64String); System::Console::PrintEndline(Util::DoubleToString(765.432, "%5.2f"));
String Helper Objects and Helper Functions
The header file murl_util_string.h
provides several helper functions for string operations. A detailed description for all methods can be found in the API reference on the Murl Util String Functions
page.
Static String Objects:
Util::StaticEmptyString()
Util::StaticWhitespaceString()
Util::StaticEmptyStringArray()
Sort StringArray:
Split String in a StringArray or in a StringIndex:
Join StringArray elements or StringIndex elements to a String:
Util::JoinStringArray()
Util::JoinStringIndex()
Trim StringArray:
Parse String:
Util::GetLine()
Util::GetWord()
File Path and File Name:
Util::GetFilePath()
Util::GetFileName()
Util::GetFileExtension()
Util::StripExtension()
Util::StripPathAndExtension()
Util::JoinPaths()
Util::GetUnixPath()
Util::GetWindowsPath()
Util::GetNormalizedPath()
Util::GetRelativePath()
Util::GetAbsolutePath()
C++ Name:
Util::HasCppScope()
Util::GetCppScope()
Util::StripCppScope()
Util::HasScope()
Util::GetScope()
Util::StripScope()
Attributes:
Util::StripIndex()
Util::StripCount()
Util::IsIdValid()
Numeric/Alphanumeric:
Util::IsNumeric()
Util::IsAlphaNumeric()
Single Character Verification:
Util::IsDigit()
Util::IsAlpha()
Util::IsAlphaNumeric()
Util::IsPunctuation()
Util::IsSpace()
Util::IsHexDigit()
Util::IsControl()
Array Class
The C++ programming language does not provide a proper container array class. To compensate for that the class Array
declared in the header murl_array.h
is provided.
The Array
examples are implemented in the following method:
void App::ContainerLogic::ArrayDemo()
Array Template Class
The following predefined Array
types exist to simplify the creation of Array
instances for frequently used data types:
UInt8Array
SInt8Array
UInt16Array
SInt16Array
UInt32Array
SInt32Array
UInt64Array
SInt64Array
BoolArray
StringArray
RealArray
FloatArray
DoubleArray
The predefined Array
types spare the usage of the "unhandsome" notation with angle brackets (e.g. Array<UInt32>
.
An Array
object can easily be instantiated wherever needed:
UInt32Array myArray;
Such an instantiated Array
is initially empty (IsEmpty()
):
if (myArray.IsEmpty()) { System::Console::Print("myArray is Empty\n"); }
Append an element to the end of this Array (Add()
):
myArray.Add(17); myArray.Add(42); myArray.Add(54); myArray.Add(33); myArray.Add(22); System::Console::Print("myArray has %d elements\n", myArray.GetCount());
Get the element at a specified position in this Array ([]
):
- Note
- Note that the iteration through NTL containers is generally index based.
for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); } System::Console::Print("\n");
Overwrite an element at a specific position in this Array (Set()
):
myArray.Set(2, 44); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Insert an element at a specific position in this Array (Insert()
):
myArray.Insert(2, 55); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Remove one or more element(s) at a specific position in this Array (Remove()
):
myArray.Remove(3, 2); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Remove several elements (Remove()
):
Note that it is not possible to remove multiple elements within one loop because the iterator value is off after the removal of an element. One solution for this problem is the usage of an additional Array which contains the indices of the elements which should be removed.
Caution! The indices must be sorted in ascending order.
The following example removes all odd numbers:
SInt32Array indicesToRemove; for (UInt32 i = 0; i < myArray.GetCount(); i++) { if (myArray[i] & 1) { indicesToRemove.Add(i); } } myArray.Remove(indicesToRemove); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Find the index of an element in this Array (Find()
, FindLast()
):
The method returns the index of the first occurrence of the specified element in the range of 0 .. (GetLength() – 1)
or a negative number if the element does not occur. Optionally the search can be started from a given index in the valid range.
myArray.Add(44); myArray.Add(22); SInt32 findIndex; findIndex = myArray.Find(17); System::Console::Print("Find 17 at %d\n", findIndex); findIndex = myArray.Find(22); System::Console::Print("Find 22 at %d\n", findIndex); findIndex = myArray.FindLast(22); System::Console::Print("FindLast 22 at %d\n", findIndex);
The method IsIndexValid()
can be used to check if a given index value is valid.
UInt32 i=0; System::Console::Print("myArray contains [ "); while (myArray.IsIndexValid(i)) { System::Console::Print("%d ", myArray[i++]); } System::Console::Print("]\n");
Duplicate Arrays (Array()
, =
):
UInt32Array my2ndArray(myArray); UInt32Array my3rdArray; my3rdArray = myArray;
Concatenate Arrays (Add()
):
myArray.Add(my2ndArray); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Get the first element in this Array (Bottom()
)::
System::Console::Print("Bottom element is %d\n", myArray.Bottom());
Get the last element in this Array (Top()
):
System::Console::Print("Top element is %d\n", myArray.Top());
Remove the last element in this Array(Pop()
):
System::Console::Print("Top element was %d\n", myArray.Pop());
Clear Arrays (Clear()
, Empty()
):
Clear()
removes all of the elements from this Array and frees all internal allocated memory. Empty()
removes all of the elements from this Array but keeps the internal allocated memory for further usage. Using the method Empty()
can be considerably faster, if the same Array object is cleared and refilled with new elements several times.
my2ndArray.Clear(); my3rdArray.Empty();
The number of Array elements can also be set with SetCount()
. The default constructor is used for each element.
Caution! No default constructors exist for primitive data types which will cause uninitialized elements (e.g. UInt32Array
, FloatArray
etc.). The StringArray
elements will be initialized with empty Strings by the default constructor provided from the String
class.
FloatArray myFloatArray; myFloatArray.SetCount(10); for (UInt32 i = 0; i < myFloatArray.GetCount(); i++) { System::Console::Print("Element %d has value %f\n", i, myFloatArray[i]); }
Manually initialize all elements after SetCount()
:
myFloatArray.SetCount(10); for (UInt32 i = 0; i < myFloatArray.GetCount(); i++) { myFloatArray[i] = 0; } for (UInt32 i = 0; i < myFloatArray.GetCount(); i++) { System::Console::Print("Element %d has value %f\n", i, myFloatArray[i]); }
Further Array Methods
Most of the shown Array methods also exist with additional parameter sets. A complete list of all methods can be found in the Array API reference in the section Murl Container Classes
or directly in the header files murl_array.h
and murl_object_array.h
.
ObjectArray Template Class
As previously mentioned, NTL containers do not use copy constructors for internal memory movements to improve performance. Object containers like the ObjectArray
from murl_object_array.h
should be used to store big structs and objects.
class MyObject { public: // Default Constructor MyObject() : mNumber(0) , mStatus(false) { } String GetDescription() const { String description = Util::UInt32ToString(mNumber); description += " " + Util::BoolToString(mStatus); description += " '" + mName + "'"; return description; } UInt32 mNumber; Bool mStatus; String mName; };
Generally all methods of the Array
class are also provided by the ObjectArray
class, except for a few.
ObjectArray<MyObject> myObjectArray; myObjectArray.SetCount(3); System::Console::Print("myObjectArray has %d elements\n", myObjectArray.GetCount()); myObjectArray[0].mName = "Element 0"; myObjectArray[1].mName = "Element 1"; myObjectArray[2].mName = "Element 2"; for (UInt32 i = 0; i < myObjectArray.GetCount(); i++) { System::Console::Print("Element %d is %s\n", i, myObjectArray[i].GetDescription().Begin()); }
Add an existing object:
MyObject anotherObject; anotherObject.mNumber = 42; anotherObject.mStatus = true; anotherObject.mName = "New One!"; myObjectArray.Add(anotherObject); System::Console::Print("myObjectArray has %d elements\n", myObjectArray.GetCount()); for (UInt32 i = 0; i < myObjectArray.GetCount(); i++) { System::Console::Print("Element %d is %s\n", i, myObjectArray[i].GetDescription().Begin()); }
SetCount(3)
creates three objects and stores them in the ObjectArray
. The method Add(anotherObject)
creates a copy of the object anotherObject
and stores this copy in the ObjectArray
.
The method Remove()
can be used to remove an object from the ObjectArray
and to free the memory of it. The ObjectArray
frees the memory for all of its objects automatically when the ObjectArray
is destroyed.
Specific ObjectArray Methods
If the copy constructor of an object is private, the object obviously cannot be added with the above shown method Add(anotherObject)
. Therefore ObjectArrays
support the addition of elements which are directly allocated with "new"
. The ObjectArray
takes the owner ship of the object and frees the memory using "delete"
in due course.
myObjectArray.Add(new MyObject); myObjectArray.Set(1, new MyObject); myObjectArray.Insert(2, new MyObject);
It is also possible to remove an object including owner ship from an ObjectArray
. Such an object must then be released explicitly with "delete"
(Detach()
, PopDetach()
):
delete myObjectArray.Detach(1); delete myObjectArray.PopDetach();
Simple Container vs. Object Container
As already mentioned in subsection NTL Container vs. STL Container, object containers internally store references to the instances of their elements, which therefore do not need to be moved in the memory. The following example should illustrate the differences between object containers and simple containers:
We create a simple Array arrayNamesA
and ObjectArray arrayNamesB
and fill both with the same data. In addition we create an object reference objRefA
/ objRefB
and an object copy objCopyA
/ objCopyB
from one element.
Array<MyObject> arrayNamesA; arrayNamesA.SetCount(3); arrayNamesA[0].mName = "Pac Man A"; arrayNamesA[1].mName = "Super Mario A"; arrayNamesA[2].mName = "Lara Croft A"; MyObject& objRefA = arrayNamesA.Get(1); MyObject objCopyA = arrayNamesA.Get(1); ObjectArray<MyObject> arrayNamesB; arrayNamesB.SetCount(3); arrayNamesB[0].mName = "Pac Man B"; arrayNamesB[1].mName = "Super Mario B"; arrayNamesB[2].mName = "Lara Croft B"; MyObject& objRefB = arrayNamesB.Get(1); MyObject objCopyB = arrayNamesB.Get(1);
We use Debug::Trace
to show the content:
Debug::Trace("Initial:"); Debug::Trace(" objRefA: " + objRefA.GetDescription()); Debug::Trace("objCopyA: " + objCopyA.GetDescription()); Debug::Trace(" objRefB: " + objRefB.GetDescription()); Debug::Trace("objCopyB: " + objCopyB.GetDescription());
All variables show the same content but point to different memory regions.
As next step we change the array elements located at index position 1 and print again the variable content using Debug::Trace
.
arrayNamesA[1].mName = "Giana Sisters A"; arrayNamesB[1].mName = "Giana Sisters B";
As expected objRefA
and objRefB
show the altered values while the variables objCopyA
and objCopyB
show the "old" values.
Now we remove the element located at index position 0 from each array.
arrayNamesA.Remove(0); arrayNamesB.Remove(0);
The reference objRefB
points to the correct element in the array. The reference objRefA
points still to the element with index position 1 although the correct element now has index position 0 due to the delete operation.
Another advantage of object containers is the usually better performance of delete and insert operations because lesser memory needs to be moved (only the pointers to the elements needs to be moved and not the whole elements itself).
Index Class
An Index
container can be used to store and retrieve elements. The container is using a hash algorithm internally to accelerate the search process. Each element is associated with an index value. The classes Index
and ObjectIndex
are implemented in the header files murl_index.h
and murl_object_index.h
and utilize the base class IndexBase
from murl_index_base.h
.
The Index
examples are implemented in the following method:
void App::ContainerLogic::IndexDemo()
Index Template Class
The following predefined Index
types exist to simplify the creation of Index instances for frequently used data types:
UInt8Index
SInt8Index
UInt16Index
SInt16Index
UInt32Index
SInt32Index
UInt64Index
SInt64Index
StringIndex
RealIndex
FloatIndex
DoubleIndex
The predefined types spare the usage of the "unhandsome" notation with angle brackets (e.g. Index<String>
).
Creation of an Index
object:
StringIndex myStringIndex; myStringIndex.Add("null"); myStringIndex.Add("one"); myStringIndex.Add("two"); myStringIndex.Add("three"); myStringIndex.Add("four"); myStringIndex.Add("five"); // "null"->0, "one"->1, "two"->2, "three"->3, "four"->4, "five"->5 System::Console::Print("myStringIndex one is %d\n", myStringIndex.Find("one")); System::Console::Print("myStringIndex two is %d\n", myStringIndex.Find("two")); System::Console::Print("myStringIndex four is %d\n", myStringIndex.Find("four"));
It is possible to add the same element several times:
myStringIndex.Add("two"); // "null"->0, "one"->1, "two"->2, "three"->3, "four"->4, "five"->5, "two"->6 System::Console::Print("myStringIndex has %d elements\n", myStringIndex.GetCount()); for (UInt32 i = 0; i < myStringIndex.GetCount(); i++) { System::Console::Print("Element %s has index %d\n", myStringIndex[i].Begin(), i); }
The method RemoveKey()
removes each appearance of the specified element from the container:
myStringIndex.RemoveKey("two"); // "null"->0, "one"->1, "three"->2, "four"->3, "five"->4 System::Console::Print("myStringIndex has %d elements\n", myStringIndex.GetCount()); for (UInt32 i = 0; i < myStringIndex.GetCount(); i++) { System::Console::Print("Element %s has index %d\n", myStringIndex[i].Begin(), i); }
The method Remove()
removes the element at the specific position:
myStringIndex.Remove(3); // "null"->0, "one"->1, "three"->2, "five"->3 System::Console::Print("myStringIndex has %d elements\n", myStringIndex.GetCount()); for (UInt32 i = 0; i < myStringIndex.GetCount(); i++) { System::Console::Print("Element %s has index %d\n", myStringIndex[i].Begin(), i); }
To accomplish an Index
with unique elements the method FindAdd()
needs to be used. The method will add an element only if such an element doesn't exist in the container already. This avoids multiple occurrences of the same element:
myStringIndex.Pop(); myStringIndex.FindAdd("two"); myStringIndex.FindAdd("three"); // "null"->0, "one"->1, "three"->2, "two"->3 System::Console::Print("myStringIndex has %d elements\n", myStringIndex.GetCount()); for (UInt32 i = 0; i < myStringIndex.GetCount(); i++) { System::Console::Print("Element %s has index %d\n", myStringIndex[i].Begin(), i); }
Index Hash
Classes need to provide a public GetHashValue()
method in order to be able to use the class objects as index elements. The method should calculate and return a proper hash value.
Example: Hash of a Data
objects from murl_data.h
:
UInt32 GetHashValue() const { return Util::Hash::GetMemoryHashValue(mData, mByteSize); }
Further Index Methods
The Index
methods are very similar to the Array
methods e.g. Set()
, Insert()
, Remove()
, etc.
The methods Find()
, FindLast()
, FindNext()
and FindPrev()
can be used to find multiple occurrences of elements.
All methods which are containing the term "Put"
in their names are only relevant if the method Unlink()
is used. The method Unlink()
can be used to exclude elements from search operations.
A complete list of all methods can be found in the API reference
or directly in the header files murl_index_base.h
, murl_index.h
and murl_object_index.h
.
ObjectIndex Template Class
As previously mentioned, NTL containers do not use copy constructors for internal memory movements to improve performance. Object containers like the ObjectIndex
from murl_object_index.h
should be used to store big structs and objects.
Generally all methods of the Index
class are also provided by the ObjectIndex
class, except for a few.
Queue Class
The Queue
class can be used to create FIFO containers and allow fast adding and removing of elements at the beginning and at the end of a sequence.
The classes Queue
and ObjectQueue
are implemented in the header files murl_queue.h
and murl_object_queue.h
.
The Queue examples are implemented in the following method:
void App::ContainerLogic::QueueDemo()
Queue Template Class
Create a Queue
object
Queue<String> myStringQueue;
The following predefined Queue types exist to simplify the creation of Queue instances for frequently used data types:
UInt8Queue
SInt8Queue
UInt16Queue
SInt16Queue
UInt32Queue
SInt32Queue
UInt64Queue
SInt64Queue
BoolQueue
StringQueue
RealQueue
FloatQueue
DoubleQueue
The predefined Queue types spare the usage of the "unhandsome" notation with angle brackets:
StringQueue myStringQueue;
Add some values to the queue (AddHead()
, AddTail()
):
myStringQueue.AddTail("item 1"); myStringQueue.AddTail("item 2"); myStringQueue.AddTail("item 3"); myStringQueue.AddHead("item 0");
Get the first element by reference (Head()
, Tail()
):
String& head = myStringQueue.Head(); System::Console::PrintEndline(head);
Get the element at a specified position ([]
):
String& item1 = myStringQueue[1]; System::Console::PrintEndline(item1);
Delete the first element (DropHead()
, DropTail()
):
myStringQueue.DropHead();
Get and remove the first element (DropGetHead()
, DropGetTail()
) until the queue is empty:
while (!myStringQueue.IsEmpty()) { head = myStringQueue.DropGetHead(); System::Console::PrintEndline(head); }
ObjectQueue Template Class
As previously mentioned, NTL containers do not use copy constructors for internal memory movements to improve performance. Object containers like the ObjectQueue
from murl_object_queue.h
should be used to store big structs and objects.
Most methods of the Queue class are also provided by the ObjectQueue
class.
Map Class
A map object can be used to map a key to one or more elements. The container is using a hash algorithm internally to accelerate the search process. The class Map
and ObjectMap
are implemented in the header files murl_map.h
and murl_object_map.h
and utilize the base class MapBase
from murl_map_base.h
.
The Map examples are implemented in the following method:
void App::ContainerLogic::MapDemo()
Map Template Class
Create a Map
object, e.g. association of a String
key to a Double
value:
Map<String, Double> nameToDouble; nameToDouble.Add("null", 0.0); nameToDouble.Add("pi", Math::PI); nameToDouble.Add("double", 2.0); nameToDouble.Add("halve", 0.5);
Get an element from a Map (GetKey()
, []
):
System::Console::Print("nameToDouble has %d elements\n", nameToDouble.GetCount()); for (UInt32 i = 0; i < nameToDouble.GetCount(); i++) { System::Console::Print("Key %s ", nameToDouble.GetKey(i).Begin()); System::Console::Print("has value %f\n", nameToDouble[i]); }
Find an element (Find()
):
Note that the iteration through NTL containers is generally index based.
SInt32 index = nameToDouble.Find("double"); if (index >= 0) { System::Console::Print("double value %f\n", nameToDouble[index]); } index = nameToDouble.Find("something"); if (index < 0) { System::Console::PrintEndline("something not found"); }
The above shown approach has the advantage that you can easily react if a key does not exist in the Map.
A simpler but also more "dangerous" approach is the use of the method Get()
.
Caution! In this case the application will stop with an ASSERT
if the key does not exist!
System::Console::Print("pi is %f", nameToDouble.Get("pi")); System::Console::Print("halve is %f", nameToDouble.Get("halve"));
The class provides also a "defused" variant of the method Get()
which allows to specify a default value if the key does not exist. This default value is then added to the Map
:
System::Console::Print("quater is %f\n", nameToDouble.Get("quater", 0.25)); System::Console::Print("nameToDouble has %d elements\n", nameToDouble.GetCount()); for (UInt32 i = 0; i < nameToDouble.GetCount(); i++) { System::Console::Print("Key %s has value %f\n", nameToDouble.GetKey(i).Begin(), nameToDouble[i]); }
It is possible to add more than one value to the same key:
nameToDouble.Add("odd", 1.0); nameToDouble.Add("odd", 3.0); nameToDouble.Add("odd", 7.0); index = nameToDouble.Find("odd"); while (index >= 0) { System::Console::Print("Found 'odd' with value %f\n", nameToDouble[index]); index = nameToDouble.FindNext(index); }
To avoid multiple values per key the method FindAdd()
needs to be used:
nameToDouble.RemoveKey("odd"); nameToDouble.FindAdd("halve", 0.5); nameToDouble.FindAdd("ten", 10.0); System::Console::Print("nameToDouble has %d elements\n", nameToDouble.GetCount()); for (UInt32 i = 0; i < nameToDouble.GetCount(); i++) { System::Console::Print("Key %s has value %f\n", nameToDouble.GetKey(i).Begin(), nameToDouble[i]); }
Further Map Methods
The Map
methods are very similar to the Array
/ Index
methods e.g. Insert()
, Remove()
, etc.
The methods Find()
, FindLast()
, FindNext()
and FindPrev()
can be used to find multiple occurrences of elements.
All methods which are containing the term "Put"
in their names are only relevant if the method Unlink()
is used. The method Unlink()
can be used to exclude elements from search operations.
A complete list of all methods can be found in the API reference
or directly in the header files murl_map_base.h
, murl_map.h
and murl_object_map.h
.
ObjectMap Template Class
As previously mentioned, NTL containers do not use copy constructors for internal memory movements to improve performance. Object containers like the ObjectMap
from murl_object_map.h
should be used to store big structs and objects.
Generally all methods of the Map
class are also provided by the ObjectMap
class, except for a few.
Time and Date
The Murl Engine provides methods related to the system time in the header file murl_system_time.h
.
The time examples are implemented in the following method:
void TimeDemo(const Logic::IState* state)
The method GetNow()
returns the current time as System::Time
object:
System::Time currentTime = System::Time::GetNow();
System::Time
objects store the time as the number of seconds that have elapsed since 00:00, 1 January 1970. System::Time
objects can easily be converted into readable date objects (System::DateTime
):
System::DateTime currentDate(currentTime); System::Console::Print("Date %4d-%02d-%02d", currentDate.mYear, currentDate.mMonth, currentDate.mDay); System::Console::Print(" Time %02d:%02d:%02d\n", currentDate.mHour, currentDate.mMinute, currentDate.mSecond);
Get the boot time of the computer system (GetBootTime()
):
System::Time bootTime = state->GetEngineConfiguration()->GetBootTime(); System::DateTime bootDate(bootTime); System::Console::Print("Boot %4d-%02d-%02d", bootDate.mYear, bootDate.mMonth, bootDate.mDay); System::Console::Print(" Time %02d:%02d:%02d\n", bootDate.mHour, bootDate.mMinute, bootDate.mSecond);
System::Time
objects provide also calculation operators:
System::Time upTime = currentTime - bootTime; System::Console::Print("Uptime %llu seconds", upTime.GetSeconds()); System::Console::Print(" %llu milliseconds\n", upTime.GetMilliSeconds());
Print out the system up time:
System::DateTime upDate(upTime); System::Console::Print("Uptime %d Months %d Days", upDate.mMonth - 1, upDate.mDay - 1); System::Console::Print(" %02d:%02d:%02d\n", upDate.mHour, upDate.mMinute, upDate.mSecond);
GetTickCount
The method GetTickCount()
can be used to create System::Time
objects that are not affected by changes in the system time-of-day clock. Adjustments of the system time have no effect to this time base. If the system is rebootet, this time base typically is also reset. For relative time measurements in the application this method should be preferred.
Further Time Methods
A complete list of all methods can be found in the API reference (System::Time
, System::DateTime
) or directly in the header murl_system_time.h
.
Note that it is also possible to compare and sort System::Time
objects; see also next section Sorting.
Time Measurement (Benchmarks)
The file murl_debug_time.h
provides convenient methods for time measurements:
- MURL_TRACE_TIME_BEGIN(key) and MURL_TRACE_TIME_END(key, level, message)
- MURL_ERROR_TIME_BEGIN(key) and MURL_ERROR_TIME_END(key, message)
The parameter key
is used to assign the BEGIN and END methods to each other. Hence also interleaved calls are possible.
MURL_TRACE_TIME_BEGIN(t1); CalculateDelaunayTriangulation(); MURL_TRACE_TIME_END(t1,0, "CalculateDelaunayTriangulation() "); MURL_ERROR_TIME_BEGIN(t2); CalculateConvexHull(); MURL_ERROR_TIME_END(t2, "CalculateConvexHull() ");
The messages are printed in the console window as debug messages or as "error messages" depending on the used methods.
- Note
- Sometimes the logic tick duration (
GetCurrentTickDuration()
) is used as benchmark value. Please note that the Murl Engine by default is averaging time differences between logic ticks and is using an upper limit of one second for the maximum logic tick duration. The methodIAppConfiguration::SetClockAveragingFactor()
can be used to change the averaging factor (1
means no averaging). The bounds for the logic tick duration can be adjusted with the methodIEngineConfiguration::SetBoundsForLogicTickDuration()
.
Sorting
The Murl Engine provides methods to sort data in the header file murl_util_sort.h
.
Sort Standard Data Types
The best way to sort standard data type elements is the usage of Arrays
and the method Util::SortArray
:
SInt32Array numbers; numbers.Add(45); numbers.Add(23); numbers.Add(67); numbers.Add(98); numbers.Add(12); Util::SortArray(numbers, true); for (UInt32 i = 0; i < numbers.GetCount(); i++) { System::Console::Print("Number %d has value %d\n", i, numbers[i]); }
The method Util::SortArray
is available for the following Array types:
UInt64Array
, SInt64Array
, UInt32Array
, SInt32Array
, RealArray
, DoubleArray
and StringArray
StringArray names; names.Add("home"); names.Add("friends"); names.Add("all"); names.Add("go"); names.Add("now"); Util::SortArray(names, true); for (UInt32 i = 0; i < names.GetCount(); i++) { System::Console::Print("Name %d has value %s\n", i, names[i].Begin()); }
Sort Custom Objects
To sort other elements it is necessary to implement your own comparison function.
In the following example we will implement a simple class to store receipts with a value and a timestamp.
To be able to sort an Array of such receipt objects, we need to implement a comparison function which can compare two instances (source1
and source2
).
We will implement two comparison functions: one to compare the timestamps (CompareTime
) and one to compare the values (CompareValue
).
The return value of a comparison function needs to be:
Null
if source1
is equal to source2
.
Negative
if source1
is less than source2
.
Positive
if source1
is greater than source2
.
class MyReceipt { public: // Default Constructor MyReceipt() : mValue(0) { } MyReceipt(const System::Time& time, Double value) : mTime(time) , mValue(value) { } static SInt32 CompareTime(const MyReceipt* source1, const MyReceipt* source2) { if (source1->mTime > source2->mTime) { return 1; } if (source1->mTime < source2->mTime) { return -1; } return 0; } static SInt32 CompareValue(const MyReceipt* source1, const MyReceipt* source2) { if (source1->mValue > source2->mValue) { return 1; } if (source1->mValue < source2->mValue) { return -1; } return 0; } String GetDescription() const { System::DateTime date(mTime); String description = Util::DoubleToString(mValue, "% 8.2f"); description += " " + Util::UInt32ToString(date.mYear, "%4d"); description += "-" + Util::UInt32ToString(date.mMonth, "%02d"); description += "-" + Util::UInt32ToString(date.mDay, "%02d"); description += " " + Util::UInt32ToString(date.mHour, "%02d");; description += ":" + Util::UInt32ToString(date.mMinute, "%02d");; description += ":" + Util::UInt32ToString(date.mSecond, "%02d");; return description; } System::Time mTime; Double mValue; };
Create receipts:
Array<MyReceipt> myReceipts; System::Time time = System::Time::GetNow(); myReceipts.Add(MyReceipt(time, 100.5)); myReceipts.Add(MyReceipt(time + System::Time::FromSeconds(5000), 203.19)); myReceipts.Add(MyReceipt(time - System::Time::FromSeconds(4000), 1060.75)); myReceipts.Add(MyReceipt(time + System::Time::FromSeconds(2000), 2007.25)); myReceipts.Add(MyReceipt(time - System::Time::FromSeconds(8000), 412.34));
Sort receipts according to timestamps:
Util::SortArray(myReceipts, MyReceipt::CompareTime); for (UInt32 i = 0; i < myReceipts.GetCount(); i++) { System::Console::Print("Receipt %d is %s\n", i, myReceipts[i].GetDescription().Begin()); }
Sort receipts according to values:
Util::SortArray(myReceipts, MyReceipt::CompareValue); for (UInt32 i = 0; i < myReceipts.GetCount(); i++) { System::Console::Print("Receipt %d is %s\n", i, myReceipts[i].GetDescription().Begin()); }
- Note
- Hint: To sort the array in reverse order simply create additional comparison functions with inverted return values.
Further Sort Methods
It is also possible to sort data arrays without using the Array
container class; see Util::QuickSort()
, Util::BubbleSort()
and Util::BinarySearch()
.
SInt32 compare (const SInt32 * a, const SInt32 * b) { return (*a - *b ); } void sort() { SInt32 a[] = {3,4,8,2,1}; Util::QuickSort(a, 5, compare); for (int i=0; i<5; i++) Debug::Trace(Util::SInt32ToString(a[i])); }
A complete list of all methods can be found in the API reference (Sort Functions
) or directly in the header file murl_util_sort.h
.
Mathematical Functions
The Murl Engine provides mathematical functions and constants in murl_math.h
, murl_math_types.h
and murl_math_limits.h
.
Functions
The header murl_math.h
provides the following mathematical functions
:
- trigonometric functions
- hyperbolic function
- exponential and logarithmic functions
- raising to powers functions
- rounding and other functions
Constants
The header murl_math_types.h
provides the following constants
:
- Euler's number and pi and several multiplied/divided pi values.
- Conversion coefficients to convert between degree and radiant.
- Conversion coefficients to convert between metric and inch.
Range of Numbers
The header file murl_math_limits.h
provides the following limit functions
:
Math::Limits::Min()
Math::Limits::Max()
Math::Limits::NaN()
Math::Limits::Infinity()
Math::Limits::Epsilon()
Further Mathematical Classes
The Murl Engine provides further classes to work with vectors, matrices, quaternions etc. A complete list of all math classes can be found in the Math API reference
.
Data Class
Some functions return or expect Data
objects: e.g. if a file is read or if data is received from the network. The Data
class simplifies the handling of raw data and is specified in the header file murl_data.h
.
Using Data
objects avoids in most cases the usage of "unsafe" functions like memcpy()
.
Example:
Data myData("Hello World!"); System::Console::PrintHex(myData); myData.ResizeData(30); System::Console::PrintHex(myData); Data otherData("Spraylight"); myData += otherData; System::Console::PrintHex(myData); Data moreData("Murl"); myData.CopyDataFrom(moreData, 15); System::Console::PrintHex(myData);
Further Data Classes and Methods
The Data
class is derived from MutableData
which on the other hand is derived from ConstData
. This hierarchy is useful to define who is providing the data and who has the owner ship of the raw data.
The method Util::StaticEmptyData()
provides an empty Data object.
A complete list of all Data
, MutableData
and ConstData
methods can be found in the Data API reference
or in the header file murl_data.h
.
Pointer
Usually it is not necessary to use pointers at all when developing Murl Engine applications.
However, if it is inevitable to allocate objects the following classes may be helpful:
AutoPointer
The class AutoPointer
is defined in the header file murl_auto_pointer.h
.
To a certain degree an AutoPointer
takes the memory management owner ship for an object. If an AutoPointer
is destroyed (released), the AutoPointer
ensures that also the memory of the object to which it points gets released automatically.
Be careful when using AutoPointer
: When a pointer is assigned to another pointer also the owner ship is transferred and the source pointer is suddenly 0 after the assignment!
There is a golden rule when working with AutoPointers
: There can be only one! ;-)
SharedPointer / WeakPointer
As previously mentioned, due to compatibility issues the Murl Engine does not use the C++ library Boost.
However, the framework provides the two classes SharedPointer
and WeakPointer
. The usage is exactly the same as with the Boost library.
The SharedPointer
class is defined in the header file murl_shared_pointer.h
.
The WeakPointer
class is defined in the header file murl_weak_pointer.h
.
Like the AutoPointer
, a SharedPointer
releases automatically the memory of the object to which it points. But - in contrast to the AutoPointer
, multiple SharedPointer
objects may point to the same object. A reference counter counts the total number of smart pointers for each object. The counter is incremented on pointer assignments and decremented on pointer destructions. When the counter reaches zero the object gets released automatically.
A WeakPointer
is a pointer to a SharedPointer
object which does not prevent the release of the object. The reference counter is not incremented for WeakPointers. You can request a SharedPointer
object from the WeakPointer
object when needed. If the object has been released already, the returned SharedPointer
will be 0. If the object still exists, the reference counter is increased and the returned SharedPointer
object points to the object.
Further documentation and examples can be found in the documentation of the Boost library (www.boost.org/doc).
Utility Functions
The following subsections list some useful utility functions. A complete list can be found in the API reference on the pages Murl::Util Functions, Murl::Math Functions and Murl::System::CLib Functions.
Util & Math Functions
A lot of utility functions are implemented as templates in the header files murl_util.h
and murl_math.h
; e.g.:
Math::Abs()
Math::Max()
Math::Min()
Math::Clamp()
Math::IsEqual()
Util::RoundToNextPowerOfTwo()
Util::IsPowerOfTwo()
etc.
System::CLib Functions
Forwards to the C library are defined in the header file murl_system_clib.h
; e.g.:
System::CLib::PrintToString()
System::CLib::ScanString()
System::CLib::StrCmp()
System::CLib::StrLen()
etc.
Memory Manipulation
If it is inevitable to directly manipulate memory regions, the following functions can be used:
Util memory manipulation functions defined in murl_util.h
:
Util::MemClear()
Util::MemSet()
Util::MemCopy()
Util::MemCopyArray()
Util::MemMove()
Util::MemCompare()
Util::Fill()
Util::FillArray()
System::CLib memory manipulation functions defined in murl_system_clib.h
(forwards to the C library):
System::CLib::MemSet()
System::CLib::MemCopy()
System::CLib::MemMove()
System::CLib::MemCompare()