diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..b4475da
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,26 @@
+---
+BasedOnStyle: Microsoft
+AccessModifierOffset : -4
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: true
+#AllowShortFunctionsOnASingleLine: Inline
+AllowShortFunctionsOnASingleLine: InlineOnly
+AlwaysBreakTemplateDeclarations : true
+BreakBeforeBraces: Custom
+BraceWrapping :
+ AfterEnum : true
+ AfterCaseLabel : true
+BreakConstructorInitializers: BeforeComma
+ColumnLimit: 132
+#IndentCaseBlocks: 'true'
+IndentCaseLabels: 'true'
+PointerAlignment: Left
+UseTab : 'Never'
+
+SortIncludes : false
+
+AlignAfterOpenBracket: Align
+AllowAllArgumentsOnNextLine: false
+BinPackParameters : false
+BinPackArguments : false
+...
diff --git a/.gitignore b/.gitignore
index fd8715a..b7a2193 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@
# NuGet packages
/packages/
+Debug/
diff --git a/Fluid-HTN.UnitTests/Fluid-HTN.UnitTests.csproj b/Fluid-HTN.UnitTests/Fluid-HTN.UnitTests.csproj
index d2b9c3e..29c5000 100644
--- a/Fluid-HTN.UnitTests/Fluid-HTN.UnitTests.csproj
+++ b/Fluid-HTN.UnitTests/Fluid-HTN.UnitTests.csproj
@@ -1,6 +1,6 @@
-
+
Debug
@@ -41,10 +41,10 @@
- ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
+ ..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
- ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
+ ..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
@@ -63,23 +63,23 @@
-
-
-
{B6908CED-5C0B-415C-9564-85F66A8B5025}
Fluid-HTN
+
+
+
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
+
+
-
+
\ No newline at end of file
diff --git a/Fluid-HTN.UnitTests/packages.config b/Fluid-HTN.UnitTests/packages.config
index 2f7c5a1..f84cb10 100644
--- a/Fluid-HTN.UnitTests/packages.config
+++ b/Fluid-HTN.UnitTests/packages.config
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/Fluid-HTN.sln b/Fluid-HTN.sln
index 07a3505..f8d42c8 100644
--- a/Fluid-HTN.sln
+++ b/Fluid-HTN.sln
@@ -7,20 +7,68 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluid-HTN", "Fluid-HTN\Flui
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluid-HTN.UnitTests", "Fluid-HTN.UnitTests\Fluid-HTN.UnitTests.csproj", "{A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Fluid-HTNCPP", "Fluid-HTNCPP\Fluid-HTNCPP.vcxproj", "{0C469B65-6D39-4667-8D00-9EC963C518E3}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Fluid-HTNCPP.UnitTests", "Fluid-HTNCPP.UnitTests\Fluid-HTNCPP.UnitTests.vcxproj", "{402394C5-2D69-49F0-B2E2-239FAB4CC252}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B6908CED-5C0B-415C-9564-85F66A8B5025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6908CED-5C0B-415C-9564-85F66A8B5025}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B6908CED-5C0B-415C-9564-85F66A8B5025}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B6908CED-5C0B-415C-9564-85F66A8B5025}.Debug|x64.Build.0 = Debug|Any CPU
+ {B6908CED-5C0B-415C-9564-85F66A8B5025}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B6908CED-5C0B-415C-9564-85F66A8B5025}.Debug|x86.Build.0 = Debug|Any CPU
{B6908CED-5C0B-415C-9564-85F66A8B5025}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6908CED-5C0B-415C-9564-85F66A8B5025}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B6908CED-5C0B-415C-9564-85F66A8B5025}.Release|x64.ActiveCfg = Release|Any CPU
+ {B6908CED-5C0B-415C-9564-85F66A8B5025}.Release|x64.Build.0 = Release|Any CPU
+ {B6908CED-5C0B-415C-9564-85F66A8B5025}.Release|x86.ActiveCfg = Release|Any CPU
+ {B6908CED-5C0B-415C-9564-85F66A8B5025}.Release|x86.Build.0 = Release|Any CPU
{A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Debug|x64.Build.0 = Debug|Any CPU
+ {A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Debug|x86.Build.0 = Debug|Any CPU
{A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Release|x64.ActiveCfg = Release|Any CPU
+ {A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Release|x64.Build.0 = Release|Any CPU
+ {A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Release|x86.ActiveCfg = Release|Any CPU
+ {A6DAA5DB-FC2B-4D08-87A1-0E667A39CF21}.Release|x86.Build.0 = Release|Any CPU
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Debug|Any CPU.Build.0 = Debug|x64
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Debug|x64.ActiveCfg = Debug|x64
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Debug|x64.Build.0 = Debug|x64
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Debug|x86.ActiveCfg = Debug|Win32
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Debug|x86.Build.0 = Debug|Win32
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Release|Any CPU.ActiveCfg = Release|x64
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Release|Any CPU.Build.0 = Release|x64
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Release|x64.ActiveCfg = Release|x64
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Release|x64.Build.0 = Release|x64
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Release|x86.ActiveCfg = Release|Win32
+ {0C469B65-6D39-4667-8D00-9EC963C518E3}.Release|x86.Build.0 = Release|Win32
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Debug|Any CPU.Build.0 = Debug|x64
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Debug|x64.ActiveCfg = Debug|x64
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Debug|x64.Build.0 = Debug|x64
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Debug|x86.ActiveCfg = Debug|Win32
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Debug|x86.Build.0 = Debug|Win32
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Release|Any CPU.ActiveCfg = Release|x64
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Release|Any CPU.Build.0 = Release|x64
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Release|x64.ActiveCfg = Release|x64
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Release|x64.Build.0 = Release|x64
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Release|x86.ActiveCfg = Release|Win32
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Fluid-HTN/Fluid-HTN.csproj b/Fluid-HTN/Fluid-HTN.csproj
index d07e064..68165b7 100644
--- a/Fluid-HTN/Fluid-HTN.csproj
+++ b/Fluid-HTN/Fluid-HTN.csproj
@@ -1,5 +1,6 @@
+
Debug
@@ -13,6 +14,8 @@
512
true
+
+
true
@@ -75,6 +78,16 @@
-
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/Fluid-HTN/packages.config b/Fluid-HTN/packages.config
new file mode 100644
index 0000000..651e442
--- /dev/null
+++ b/Fluid-HTN/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Fluid-HTNCPP.UnitTests/ActionEffectsTest.cpp b/Fluid-HTNCPP.UnitTests/ActionEffectsTest.cpp
new file mode 100644
index 0000000..0296d21
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/ActionEffectsTest.cpp
@@ -0,0 +1,72 @@
+#include "pch.h"
+#include "CppUnitTest.h"
+#include "Contexts/BaseContext.h"
+#include "Effects/Effect.h"
+#include "DomainTestContext.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+using namespace FluidHTN;
+
+class TestContext : public BaseContext
+{
+ bool _Done = false;
+public:
+ bool& Done() { return _Done; }
+};
+
+namespace Microsoft::VisualStudio::CppUnitTestFramework
+{
+ template<>
+ std::wstring ToString(const EffectType& eff)
+ {
+ switch(eff)
+ {
+ case EffectType::Permanent:
+ return L"EffectType::Permanent";
+ case EffectType::PlanOnly:
+ return L"EffectType::PlanOnly";
+ case EffectType::PlanAndExecute:
+ return L"EffectType::PlanAndExecute";
+ }
+ return L"Unknown value";
+ }
+}
+namespace FluidHTNCPPUnitTests
+{
+ TEST_CLASS(ActionEffectTests)
+ {
+ public:
+
+ TEST_METHOD(SetsName_ExpectedBehavior)
+ {
+ ActionEffect a("Name", EffectType::PlanOnly, nullptr);
+
+ Assert::AreEqual("Name"s, a.Name());
+ }
+ TEST_METHOD(SetsType_ExpectedBehavior)
+ {
+ ActionEffect e("Name", EffectType::PlanOnly, nullptr);
+
+ Assert::AreEqual(EffectType::PlanOnly, e.Type());
+ }
+
+ TEST_METHOD(ApplyDoesNothingWithoutFunctionPtr_ExpectedBehavior)
+ {
+ TestContext ctx;
+ ActionEffect e("Name", EffectType::PlanOnly, nullptr);
+
+ e.Apply(ctx);
+ }
+
+ TEST_METHOD(ApplyCallsInternalFunctionPtr_ExpectedBehavior)
+ {
+ TestContext ctx;
+ ActionEffect e("Name", EffectType::PlanOnly, [=](IContext& c, EffectType ) {static_cast(c).Done() = true; });
+
+ e.Apply(ctx);
+
+ Assert::AreEqual(true, ctx.Done());
+ }
+ };
+}
diff --git a/Fluid-HTNCPP.UnitTests/BaseContextTests.cpp b/Fluid-HTNCPP.UnitTests/BaseContextTests.cpp
new file mode 100644
index 0000000..33103c0
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/BaseContextTests.cpp
@@ -0,0 +1,178 @@
+
+#include "pch.h"
+#include "CppUnitTest.h"
+#include "Effects/EffectType.h"
+#include "Contexts/BaseContext.h"
+#include "DomainTestContext.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+using namespace std::string_literals;
+
+using namespace FluidHTN;
+
+namespace FluidHTNCPPUnitTests
+{
+ TEST_CLASS(BaseContextTests)
+ {
+ TEST_METHOD( DefaultContextStateIsExecuting_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ Assert::IsTrue(ctx.GetContextState() == ContextState::Executing);
+ }
+ TEST_METHOD(InitInitializeCollections_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+
+ ctx.Init();
+
+ Assert::AreEqual(false, ctx.DebugMTR());
+ Assert::AreEqual(false, ctx.LogDecomposition());
+ Assert::AreEqual(true, ctx.MTRDebug().size() == 0);
+ Assert::AreEqual(true, ctx.LastMTRDebug().size() == 0);
+ Assert::AreEqual(true, ctx.DecompositionLog().size() == 0);
+ }
+ TEST_METHOD(InitInitializeDebugCollections_ExpectedBehavior)
+ {
+ MyDebugContext ctx;
+
+ ctx.Init();
+
+ Assert::AreEqual(true, ctx.DebugMTR());
+ Assert::AreEqual(true, ctx.LogDecomposition());
+ }
+ TEST_METHOD(HasState_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ ctx.Init();
+ ctx.SetStateDTS(DomainTestState::HasB, true,true, EffectType::Permanent);
+
+ Assert::AreEqual(false, ctx.HasStateOneParam(DomainTestState::HasA));
+ Assert::AreEqual(true, ctx.HasStateOneParam(DomainTestState::HasB));
+ }
+ TEST_METHOD(SetStatePlanningContext_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ ctx.Init();
+ ctx.SetContextState(ContextState::Planning);
+ ctx.SetStateDTS(DomainTestState::HasB, true,true, EffectType::Permanent);
+
+ Assert::AreEqual(true, (bool)ctx.GetStateDTS(DomainTestState::HasB));
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasA].size() == 0);
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasB].size() == 1);
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasB].top().First() == EffectType::Permanent);
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasB].top().Second() == 1);
+ Assert::IsTrue(ctx.GetWorldState().GetState(DomainTestState::HasB) == 0);
+ }
+ TEST_METHOD(SetStateExecutingContext_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+
+ ctx.Init();
+ ctx.SetContextState(ContextState::Executing);
+ ctx.SetStateDTS(DomainTestState::HasB, true,true, EffectType::Permanent);
+
+ Assert::AreEqual(true, ctx.HasStateOneParam(DomainTestState::HasB));
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasB].size() == 0);
+ Assert::IsTrue(ctx.GetWorldState().GetState( DomainTestState::HasB) == 1);
+ }
+
+ TEST_METHOD(GetStatePlanningContext_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+
+ ctx.Init();
+ ctx.SetContextState(ContextState::Planning);
+ ctx.SetStateDTS(DomainTestState::HasB, true,true, EffectType::Permanent);
+
+ Assert::AreEqual(0,(int) ctx.GetStateDTS(DomainTestState::HasA));
+ Assert::AreEqual(1, (int)ctx.GetStateDTS(DomainTestState::HasB));
+ }
+
+ TEST_METHOD(GetStateExecutingContext_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+
+ ctx.Init();
+ ctx.SetContextState(ContextState::Executing);
+ ctx.SetStateDTS(DomainTestState::HasB, true,true, EffectType::Permanent);
+
+ Assert::AreEqual(0,(int) ctx.GetStateDTS(DomainTestState::HasA));
+ Assert::AreEqual(1, (int)ctx.GetStateDTS(DomainTestState::HasB));
+ }
+
+ TEST_METHOD(GetWorldStateChangeDepth_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+
+ ctx.Init();
+ ctx.SetContextState(ContextState::Executing);
+ ctx.SetStateDTS(DomainTestState::HasB, true,true, EffectType::Permanent);
+ auto changeDepthExecuting = ctx.GetWorldStateChangeDepth();
+
+ ctx.SetContextState(ContextState::Planning);
+ ctx.SetStateDTS(DomainTestState::HasB, true,true, EffectType::Permanent);
+ auto changeDepthPlanning = ctx.GetWorldStateChangeDepth();
+
+ Assert::AreEqual(ctx.GetWorldStateChangeStack().size(), changeDepthExecuting.size());
+ Assert::AreEqual(0, changeDepthExecuting[(int) DomainTestState::HasA]);
+ Assert::AreEqual(0, changeDepthExecuting[(int) DomainTestState::HasB]);
+
+ Assert::AreEqual(ctx.GetWorldStateChangeStack().size(), changeDepthPlanning.size());
+ Assert::AreEqual(0, changeDepthPlanning[(int) DomainTestState::HasA]);
+ Assert::AreEqual(1, changeDepthPlanning[(int) DomainTestState::HasB]);
+ }
+
+ TEST_METHOD(TrimForExecution_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+
+ ctx.Init();
+ ctx.SetContextState(ContextState::Planning);
+ ctx.SetStateDTS(DomainTestState::HasA, true,true, EffectType::PlanAndExecute);
+ ctx.SetStateDTS(DomainTestState::HasB, true,true, EffectType::Permanent);
+ ctx.SetStateDTS(DomainTestState::HasC, true,true, EffectType::PlanOnly);
+ ctx.TrimForExecution();
+
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasA].size() == 0);
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasB].size() == 1);
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasC].size() == 0);
+ }
+
+ TEST_METHOD(TrimForExecutionThrowsExceptionIfWrongContextState_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+
+ ctx.Init();
+ ctx.SetContextState(ContextState::Executing);
+ Assert::ExpectException([&]() { ctx.TrimForExecution(); });
+ }
+ TEST_METHOD(TrimToStackDepth_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ ctx.Init();
+ ctx.SetContextState(ContextState::Planning);
+ ctx.SetStateDTS(DomainTestState::HasA, true,true, EffectType::PlanAndExecute);
+ ctx.SetStateDTS(DomainTestState::HasB, true,true, EffectType::Permanent);
+ ctx.SetStateDTS(DomainTestState::HasC, true,true, EffectType::PlanOnly);
+ auto stackDepth = ctx.GetWorldStateChangeDepth();
+
+ ctx.SetStateDTS(DomainTestState::HasA, false, true, EffectType::PlanAndExecute);
+ ctx.SetStateDTS(DomainTestState::HasB, false, true, EffectType::Permanent);
+ ctx.SetStateDTS(DomainTestState::HasC, false, true, EffectType::PlanOnly);
+ ctx.TrimToStackDepth(stackDepth);
+
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasA].size() == 1);
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasB].size() == 1);
+ Assert::IsTrue(ctx.GetWorldStateChangeStack()[(int) DomainTestState::HasC].size() == 1);
+ }
+
+ TEST_METHOD(TrimToStackDepthThrowsExceptionIfWrongContextState_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ ctx.Init();
+ ctx.SetContextState(ContextState::Executing);
+ auto stackDepth = ctx.GetWorldStateChangeDepth();
+ Assert::ExpectException([&]() { ctx.TrimToStackDepth(stackDepth); });
+ }
+ } ;
+}
diff --git a/Fluid-HTNCPP.UnitTests/DomainBuilderTests.cpp b/Fluid-HTNCPP.UnitTests/DomainBuilderTests.cpp
new file mode 100644
index 0000000..74d8a62
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/DomainBuilderTests.cpp
@@ -0,0 +1,413 @@
+#include "pch.h"
+#include "CppUnitTest.h"
+#include "CoreIncludes/BaseDomainBuilder.h"
+#include "DomainTestContext.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+using namespace FluidHTN;
+
+class DomainBuilder final : public BaseDomainBuilder
+ {
+public:
+ DomainBuilder(StringType n): BaseDomainBuilder(n){}
+};
+namespace FluidHTNCPPUnitTests
+{
+ TEST_CLASS(DomainBuilderTests)
+ {
+ TEST_METHOD(Build_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ auto ptr = builder.Pointer();
+ auto domain = *(builder.Build());
+
+ Assert::IsTrue(domain.Root() != nullptr);
+ Assert::IsTrue(ptr == domain.Root());
+ Assert::AreEqual("Test"s, domain.Root()->Name());
+ }
+
+ TEST_METHOD(BuildInvalidatesPointer_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ auto domain = *builder.Build();
+
+ Assert::ExpectException([&]() {
+ bool bRet = (builder.Pointer() == domain.Root());
+ bRet;
+ });
+ }
+
+ TEST_METHOD(Selector_ExpectedBehavior)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSelector("select test");
+ builder.End();
+
+ // Assert
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ }
+
+ TEST_METHOD(Selector_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSelector("select test");
+
+ // Assert
+ Assert::AreEqual(false, builder.Pointer()->IsTypeOf( ITaskDerivedClassName::TaskRoot));
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::SelectorCompoundTask));
+ }
+
+ TEST_METHOD(SelectorBuild_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSelector("select test");
+ Assert::ExpectException([&]() { auto domain = builder.Build(); });
+ }
+ TEST_METHOD(Selector_CompoundTask)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ SharedPtr ctask = MakeSharedPtr("compound task");
+ builder.AddCompoundTask("compound task",ctask);
+
+ // Assert
+ Assert::AreEqual(false, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::SelectorCompoundTask));
+ }
+ TEST_METHOD(Sequence_ExpectedBehavior)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSequence("Sequence test");
+ builder.End();
+
+ // Assert
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ }
+
+ TEST_METHOD(Sequence_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSequence("Sequence test");
+
+ // Assert
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::SequenceCompoundTask));
+ }
+
+ TEST_METHOD(Sequence_CompoundTask)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ SharedPtr ctask = MakeSharedPtr("sequence task");
+ builder.AddCompoundTask("compound task",ctask);
+
+ // Assert
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::SequenceCompoundTask));
+ }
+
+ TEST_METHOD(Action_ExpectedBehavior)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddAction("sequence test");
+ builder.End();
+
+ // Assert
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ }
+
+ TEST_METHOD(Action_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddAction("sequence test");
+
+ // Assert
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::PrimitiveTask));
+ }
+ TEST_METHOD(Action_PrimitiveTask)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddPrimitiveTask("sequence test");
+
+ // Assert
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::PrimitiveTask));
+ }
+
+ TEST_METHOD(PausePlanThrowsWhenPointerIsNotDecomposeAll)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ Assert::ExpectException([&]() { builder.PausePlan(); });
+ }
+
+ TEST_METHOD(PausePlan_ExpectedBehaviour)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSequence("sequence test");
+ builder.PausePlan();
+ builder.End();
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ }
+ TEST_METHOD(PausePlan_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSequence("sequence test");
+ builder.PausePlan();
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::SequenceCompoundTask));
+ }
+
+ TEST_METHOD(Condition_ExpectedBehaviour)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddCondition("test", [](IContext&) { return true; });
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ }
+
+ TEST_METHOD(ExecutingCondition_ThrowsIfNotPrimitiveTaskPointer)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ Assert::ExpectException(
+ [&]() { builder.AddExecutingCondition("test", [](IContext&) { return true; }); });
+ }
+ TEST_METHOD(ExecutingCondition_ExpectedBehavior)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddAction("test");
+ builder.AddExecutingCondition("test", [](IContext&) { return true; });
+ builder.End();
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ }
+
+ TEST_METHOD(ExecutingCondition_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddAction("test");
+ builder.AddExecutingCondition("test", [](IContext&) { return true; });
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::PrimitiveTask));
+ }
+
+ TEST_METHOD(Do_ThrowsIfNotPrimitiveTaskPointer)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ Assert::ExpectException(
+ [&]() {
+ builder.AddOperator([](IContext&) -> TaskStatus { return TaskStatus::Success; });
+ });
+ }
+
+ TEST_METHOD(Do_ExpectedBehavior)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddAction("test");
+ builder.AddOperator([](IContext&) -> TaskStatus { return TaskStatus::Success; });
+ builder.End();
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ }
+
+ TEST_METHOD(Do_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddAction("test");
+ builder.AddOperator([](IContext&) -> TaskStatus { return TaskStatus::Success; });
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::PrimitiveTask));
+ }
+
+ TEST_METHOD(Effect_ThrowsIfNotPrimitiveTaskPointer)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ Assert::ExpectException([&]() { builder.AddEffect("test", EffectType::Permanent, [](IContext&, EffectType){}); });
+ }
+
+ TEST_METHOD( Effect_ExpectedBehavior)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddAction("test");
+ builder.AddEffect("test", EffectType::Permanent, [](IContext&, EffectType){});
+ builder.End();
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ }
+
+ TEST_METHOD(Effect_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddAction("test");
+ builder.AddEffect("test", EffectType::Permanent, [](IContext&, EffectType){});
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::PrimitiveTask));
+ }
+ TEST_METHOD(Splice_ThrowsIfNotCompoundPointer)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ auto domain = *DomainBuilder("sub-domain").Build();
+ builder.AddAction("test");
+ Assert::ExpectException([&]() { builder.Splice(domain); });
+ }
+
+ TEST_METHOD(Splice_ExpectedBehavior)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ auto domain = *DomainBuilder("sub-domain").Build();
+ builder.Splice(domain);
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ }
+
+ TEST_METHOD(Splice_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ auto domain = *DomainBuilder("sub-domain").Build();
+ builder.AddSelector("test");
+ builder.Splice(domain);
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::SelectorCompoundTask));
+ }
+
+ TEST_METHOD(Slot_ThrowsIfNotCompoundPointer)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddAction("test");
+ Assert::ExpectException([&]() { builder.AddSlot(1); });
+ }
+
+ TEST_METHOD(Slot_ThrowsIfSlotIdAlreadyDefined)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSlot(1);
+ Assert::ExpectException([&]() { builder.AddSlot(1); });
+ }
+
+ TEST_METHOD(Slot_ExpectedBehavior)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSlot(1);
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+
+ auto domain = *builder.Build();
+
+ auto subDomain = *DomainBuilder("sub-domain").Build();
+ Assert::IsTrue(domain.TrySetSlotDomain(1, subDomain)); // Its valid to add a sub-domain to a slot we have defined in our domain definition, and that is not currently occupied.
+ Assert::IsTrue(domain.TrySetSlotDomain(1, subDomain) == false); // Need to clear slot before we can attach sub-domain to a currently occupied slot.
+ Assert::IsTrue(domain.TrySetSlotDomain(99, subDomain) == false); // Need to define slotId in domain definition before we can attach sub-domain to that slot.
+
+ Assert::IsTrue(domain.Root()->Subtasks().size() == 1);
+ Assert::IsTrue(domain.Root()->Subtasks()[0]->IsTypeOf(ITaskDerivedClassName::Slot));
+
+ auto slot = StaticCastPtr(domain.Root()->Subtasks()[0]);
+ Assert::IsTrue(slot->Subtask() != nullptr);
+ Assert::IsTrue(slot->Subtask()->IsTypeOf(ITaskDerivedClassName::TaskRoot));
+ Assert::IsTrue(slot->Subtask()->Name() == "sub-domain"s);
+
+ domain.ClearSlot(1);
+ Assert::IsTrue(slot->Subtask() == nullptr);
+ }
+
+ TEST_METHOD(Slot_ForgotEnd)
+ {
+ // Arrange
+ DomainBuilder builder("Test"s);
+
+ // Act
+ builder.AddSelector("test");
+ builder.AddSlot(1);
+
+ Assert::AreEqual(true, builder.Pointer()->IsTypeOf(ITaskDerivedClassName::SelectorCompoundTask));
+ }
+ };
+}
\ No newline at end of file
diff --git a/Fluid-HTNCPP.UnitTests/DomainTestContext.h b/Fluid-HTNCPP.UnitTests/DomainTestContext.h
new file mode 100644
index 0000000..eaca46d
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/DomainTestContext.h
@@ -0,0 +1,65 @@
+#pragma once
+#include "Contexts/BaseContext.h"
+
+using namespace FluidHTN;
+
+enum class DomainTestState
+{
+ HasA,
+ HasB,
+ HasC
+};
+class DomainTestWorldState : public IWorldState
+{
+ uint8_t MyState[3];
+
+public:
+ bool HasState(DomainTestState state, uint8_t value)
+ {
+ return (MyState[(int)state] == value);
+ }
+
+ uint8_t& GetState(DomainTestState state) { return MyState[(int)state]; }
+
+ void SetState(DomainTestState state, uint8_t value) { MyState[(int)state] = value; }
+
+ int GetMaxPropertyCount() { return 3; }
+};
+class DomainTestContext : public BaseContext
+{
+ bool _done = false;
+
+public:
+ DomainTestContext() { _WorldState = MakeSharedPtr(); }
+
+ bool& Done() { return _done; }
+
+ bool HasStateOneParam(DomainTestState state)
+ {
+ uint8_t one = 1;
+ return BaseContext::HasState(state, one);
+ }
+
+ void SetStateDTS(DomainTestState state, int value)
+ {
+ _WorldState->SetState(static_cast(state), static_cast(value));
+ }
+ void SetStateDTS(DomainTestState state, int value, bool dirty, EffectType eff)
+ {
+ BaseContext::SetState(static_cast(state), static_cast(value),dirty,eff);
+ }
+
+ uint8_t GetStateDTS(DomainTestState state) { return BaseContext::GetState(static_cast(state)); }
+
+};
+typedef BaseContext BaseContextType;
+
+class MyDebugContext : public DomainTestContext
+{
+public:
+ MyDebugContext()
+ {
+ _DebugMTR = true;
+ _LogDecomposition = true;
+ }
+};
diff --git a/Fluid-HTNCPP.UnitTests/DomainTests.cpp b/Fluid-HTNCPP.UnitTests/DomainTests.cpp
new file mode 100644
index 0000000..4479c50
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/DomainTests.cpp
@@ -0,0 +1,513 @@
+#include "pch.h"
+#include "CppUnitTest.h"
+#include "Contexts/BaseContext.h"
+#include "CoreIncludes/Domain.h"
+#include "Tasks/Task.h"
+#include "Tasks/CompoundTasks/CompoundTask.h"
+#include "Tasks/PrimitiveTasks/PrimitiveTask.h"
+#include "Tasks/CompoundTasks/PausePlanTask.h"
+#include "Tasks/CompoundTasks/Selector.h"
+#include "Tasks/CompoundTasks/Sequence.h"
+#include "Tasks/CompoundTasks/DecompositionStatus.h"
+#include "Effects/Effect.h"
+#include "DomainTestContext.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+using namespace FluidHTN;
+
+namespace Microsoft::VisualStudio::CppUnitTestFramework
+{
+template <>
+std::wstring ToString(ITask* eff)
+{
+ if (eff)
+ {
+ switch (eff->GetType())
+ {
+ case ITaskDerivedClassName::CompoundTask:
+ return L"ITaskDerivedClassName::CompoundTask";
+ case ITaskDerivedClassName::ITaskType:
+ return L"ITaskDerivedClassName::ITaskType";
+ case ITaskDerivedClassName::PausePlanTask:
+ return L"ITaskDerivedClassName::PausePlanTask";
+ case ITaskDerivedClassName::PrimitiveTask:
+ return L"ITaskDerivedClassName::PrimitiveTask";
+ case ITaskDerivedClassName::SelectorCompoundTask:
+ return L"ITaskDerivedClassName::SelectorCompoundTask";
+ case ITaskDerivedClassName::SequenceCompoundTask:
+ return L"ITaskDerivedClassName::SequenceCompoundTask";
+ case ITaskDerivedClassName::Slot:
+ return L"ITaskDerivedClassName::Slot";
+ case ITaskDerivedClassName::TaskRoot:
+ return L"ITaskDerivedClassName::TaskRoot";
+ }
+ }
+ return L"Unknown value";
+}
+} // namespace Microsoft::VisualStudio::CppUnitTestFramework
+
+namespace FluidHTNCPPUnitTests
+{
+TEST_CLASS(DomainTests)
+{
+ TEST_METHOD(DomainHasRootWithDomainName_ExpectedBehavior)
+ {
+ Domain domain("Test");
+ Assert::IsTrue(domain.Root() != nullptr);
+ Assert::IsTrue(domain.Root()->Name() == "Test"s);
+ }
+ TEST_METHOD(AddSubtaskToParent_ExpectedBehavior)
+ {
+ Domain domain("Test");
+ SharedPtr task1 = MakeSharedPtr("Test");
+ SharedPtr task2 = MakeSharedPtr("Test2");
+ domain.Add(task1, task2);
+ //Assert::IsTrue(std::find(task1->Subtasks().begin(), task1->Subtasks().end(), task2) != task1->Subtasks().end());
+ Assert::IsTrue(task2->Parent().get() == task1.get());
+ }
+ TEST_METHOD(FindPlanUninitializedContextThrowsException_ExpectedBehavior)
+ {
+ auto domain = MakeSharedPtr("Test");
+ SharedPtr ctx = MakeSharedPtr();
+
+ Assert::ExpectException([=]() -> DecompositionStatus {
+ TaskQueueType plan;
+ return domain->FindPlan(*ctx, plan);
+ });
+ }
+ TEST_METHOD(FindPlanNoTasksThenNullPlan_ExpectedBehavior)
+ {
+ SharedPtr ctx = MakeSharedPtr();
+ Domain domain("Test");
+ TaskQueueType plan;
+ ctx->Init();
+ auto status = domain.FindPlan(*ctx, plan);
+ Assert::IsTrue(status == DecompositionStatus::Rejected);
+ Assert::IsTrue(plan.size() == 0);
+ }
+ TEST_METHOD(AfterFindPlanContextStateIsExecuting_ExpectedBehavior)
+ {
+ SharedPtr ctx = MakeSharedPtr();
+ Domain domain("Test");
+ TaskQueueType plan;
+ ctx->Init();
+ domain.FindPlan(*ctx, plan);
+ Assert::IsTrue(ctx->GetContextState() == ContextState::Executing);
+ }
+ TEST_METHOD(FindPlan_ExpectedBehavior)
+ {
+ SharedPtr bctx = MakeSharedPtr();
+ Domain domain("Test");
+ TaskQueueType plan;
+
+ bctx->Init();
+
+ SharedPtr task1 = MakeSharedPtr("Test");
+
+ SharedPtr task2 = MakeSharedPtr("Sub-task");
+
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+
+ auto status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Succeeded);
+ Assert::IsTrue(plan.size() == 1);
+ Assert::IsTrue(plan.front()->Name() == "Sub-task"s);
+ }
+ TEST_METHOD(FindPlanTrimsNonPermanentStateChange_ExpectedBehavior)
+ {
+ SharedPtr bctx = MakeSharedPtr();
+ Domain domain("Test");
+ TaskQueueType plan;
+ bctx->Init();
+ SharedPtr task1 = MakeSharedPtr("Test");
+
+ SharedPtr task2 = MakeSharedPtr("Sub-task1");
+ SharedPtr effect1 =
+ MakeSharedPtr("TestEffect1"s, EffectType::PlanOnly, [=](IContext& ctx, EffectType t) {
+ static_cast(ctx).SetState(DomainTestState::HasA, true, true, t);
+ });
+ task2->AddEffect(effect1);
+
+ SharedPtr task3 = MakeSharedPtr("Sub-task2");
+ SharedPtr effect2 = MakeSharedPtr(
+ "TestEffect2"s,
+ EffectType::PlanAndExecute,
+ [=](IContext& ctx, EffectType t) {static_cast(ctx).SetState(DomainTestState::HasB, true, true, t); });
+ task3->AddEffect(effect2);
+
+ SharedPtr task4 = MakeSharedPtr("Sub-task3");
+ SharedPtr effect3 =
+ MakeSharedPtr("TestEffect3"s, EffectType::Permanent, [=](IContext& ctx, EffectType t) {
+ static_cast(ctx).SetState(DomainTestState::HasC, true, true, t);
+ });
+ task4->AddEffect(effect3);
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+ domain.Add(task1, task3);
+ domain.Add(task1, task4);
+ auto status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Succeeded);
+ Assert::IsTrue(bctx->GetWorldStateChangeStack()[(int)DomainTestState::HasA].size() == 0);
+ Assert::IsTrue(bctx->GetWorldStateChangeStack()[(int)DomainTestState::HasB].size() == 0);
+ Assert::IsTrue(bctx->GetWorldStateChangeStack()[(int)DomainTestState::HasC].size() == 0);
+ Assert::IsTrue(bctx->GetWorldState().GetState(DomainTestState::HasA) == 0);
+ Assert::IsTrue(bctx->GetWorldState().GetState(DomainTestState::HasB) == 0);
+ Assert::IsTrue(bctx->GetWorldState().GetState(DomainTestState::HasC) == 1);
+ Assert::IsTrue(plan.size() == 3);
+ }
+
+ TEST_METHOD(FindPlanClearsStateChangeWhenPlanIsNull_ExpectedBehavior)
+ {
+ auto bctx = MakeSharedPtr();
+ SharedPtr ctx = StaticCastPtr(bctx);
+ Domain domain("Test");
+ TaskQueueType plan;
+ bctx->Init();
+ SharedPtr task1 = MakeSharedPtr("Test");
+ SharedPtr task2 = MakeSharedPtr("Sub-task1");
+ SharedPtr effect1 =
+ MakeSharedPtr("TestEffect1"s, EffectType::PlanOnly, [=](IContext& ctx, EffectType t) {
+ static_cast(ctx).SetState(DomainTestState::HasA, true, true, t);
+ });
+ task2->AddEffect(effect1);
+
+ SharedPtr task3 = MakeSharedPtr("Sub-task2");
+ SharedPtr effect2 = MakeSharedPtr(
+ "TestEffect2"s,
+ EffectType::PlanAndExecute,
+ [=](IContext& ctx, EffectType t) { static_cast(ctx).SetState(DomainTestState::HasB, true, true, t); });
+ task3->AddEffect(effect2);
+
+ SharedPtr task4 = MakeSharedPtr("Sub-task3");
+ SharedPtr effect3 =
+ MakeSharedPtr("TestEffect3"s, EffectType::Permanent, [=](IContext& ctx, EffectType t) {
+ static_cast(ctx).SetState(DomainTestState::HasC, true, true, t);
+ });
+ task4->AddEffect(effect3);
+
+ SharedPtr task5 = MakeSharedPtr("Sub-task4");
+ SharedPtr condition = MakeSharedPtr("TestCondition"s, [=](IContext& ctx) {
+ DomainTestContext& d = (DomainTestContext&)ctx;
+ return (d.Done() == true);
+ });
+ task5->AddCondition(condition);
+
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+ domain.Add(task1, task3);
+ domain.Add(task1, task4);
+ domain.Add(task1, task5);
+ auto status = domain.FindPlan(*bctx, plan);
+ Assert::IsTrue(status == DecompositionStatus::Rejected);
+ Assert::IsTrue(bctx->GetWorldStateChangeStack()[(int)DomainTestState::HasA].size() == 0);
+ Assert::IsTrue(bctx->GetWorldStateChangeStack()[(int)DomainTestState::HasB].size() == 0);
+ Assert::IsTrue(bctx->GetWorldStateChangeStack()[(int)DomainTestState::HasC].size() == 0);
+ Assert::IsTrue(bctx->GetWorldState().GetState(DomainTestState::HasA) == 0);
+ Assert::IsTrue(bctx->GetWorldState().GetState(DomainTestState::HasB) == 0);
+ Assert::IsTrue(bctx->GetWorldState().GetState(DomainTestState::HasC) == 0);
+ Assert::IsTrue(plan.size() == 0);
+ }
+ TEST_METHOD(FindPlanIfMTRsAreEqualThenReturnNullPlan_ExpectedBehavior)
+ {
+ auto bctx = MakeSharedPtr();
+ SharedPtr ctx = StaticCastPtr(bctx);
+ Domain domain("Test");
+ TaskQueueType plan;
+ bctx->Init();
+ ctx->LastMTR().Add(1);
+
+ // Root is a Selector that branch off into task1 selector or task2 sequence.
+ // MTR only tracks decomposition of compound tasks, so our MTR is only 1 layer deep here,
+ // Since both compound tasks decompose into primitive tasks.
+ SharedPtr task1 = MakeSharedPtr("Test1");
+ SharedPtr task2 = MakeSharedPtr("Test2");
+
+ SharedPtr task3 = MakeSharedPtr("Sub-task1");
+ SharedPtr condition = MakeSharedPtr("TestCondition"s, [=](IContext& ctx) {
+ DomainTestContext& d = (DomainTestContext&)ctx;
+ return (d.Done() == true);
+ });
+ task3->AddCondition(condition);
+
+ SharedPtr task4 = MakeSharedPtr("Sub-task1");
+
+ SharedPtr task5 = MakeSharedPtr("Sub-task2");
+ SharedPtr condition2 = MakeSharedPtr("TestCondition"s, [=](IContext& ctx) {
+ DomainTestContext& d = (DomainTestContext&)ctx;
+ return (d.Done() == true);
+ });
+ task5->AddCondition(condition);
+
+ domain.Add(domain.Root(), task1);
+ domain.Add(domain.Root(), task2);
+ domain.Add(task1, task3);
+ domain.Add(task2, task4);
+ domain.Add(task2, task5);
+ auto status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Rejected);
+ Assert::IsTrue(plan.size() == 0);
+ Assert::IsTrue(ctx->MethodTraversalRecord().size() == 1);
+ Assert::IsTrue(ctx->MethodTraversalRecord()[0] == ctx->LastMTR()[0]);
+ }
+
+ TEST_METHOD(PausePlan_ExpectedBehavior)
+ {
+ auto bctx = MakeSharedPtr();
+ SharedPtr ctx = StaticCastPtr(bctx);
+ Domain domain("Test");
+ TaskQueueType plan;
+ bctx->Init();
+
+ SharedPtr task1 = MakeSharedPtr("Test1");
+ SharedPtr task2 = MakeSharedPtr("Sub-task1");
+ SharedPtr task3 = MakeSharedPtr("Sub-task2");
+
+ SharedPtr task4 = MakeSharedPtr();
+
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+ domain.Add(task1, task4);
+ domain.Add(task1, task3);
+
+ auto status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Partial);
+ Assert::IsTrue(plan.size() == 1);
+ Assert::AreEqual("Sub-task1"s, plan.front()->Name());
+ Assert::IsTrue(ctx->HasPausedPartialPlan());
+ Assert::IsTrue(ctx->PartialPlanQueue().size() == 1);
+ auto tx = StaticCastPtr(task1);
+ ITask* t1ptr = tx.get();
+ ITask* t2ptr = ctx->PartialPlanQueue().front().Task.get();
+ Assert::AreEqual(t1ptr, t2ptr);
+ Assert::AreEqual(2, ctx->PartialPlanQueue().front().TaskIndex);
+ }
+
+ TEST_METHOD(ContinuePausedPlan_ExpectedBehavior)
+ {
+ auto bctx = MakeSharedPtr();
+ SharedPtr ctx = StaticCastPtr(bctx);
+ Domain domain("Test");
+ TaskQueueType plan;
+ bctx->Init();
+
+ SharedPtr task1 = MakeSharedPtr("Test1");
+ SharedPtr task2 = MakeSharedPtr("Sub-task1");
+ SharedPtr task3 = MakeSharedPtr("Sub-task2");
+
+ SharedPtr task4 = MakeSharedPtr();
+
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+ domain.Add(task1, task4);
+ domain.Add(task1, task3);
+
+ auto status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Partial);
+ Assert::IsTrue(plan.size() == 1);
+ Assert::AreEqual("Sub-task1"s, plan.front()->Name());
+ Assert::IsTrue(ctx->HasPausedPartialPlan());
+ Assert::IsTrue(ctx->PartialPlanQueue().size() == 1);
+ auto tx = StaticCastPtr(task1);
+ ITask* t1ptr = tx.get();
+ ITask* t2ptr = ctx->PartialPlanQueue().front().Task.get();
+ Assert::AreEqual(t1ptr, t2ptr);
+ Assert::AreEqual(2, ctx->PartialPlanQueue().front().TaskIndex);
+
+ status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Succeeded);
+ Assert::IsTrue(plan.size() == 1);
+ Assert::AreEqual("Sub-task2"s, plan.front()->Name());
+ }
+
+ TEST_METHOD(NestedPausePlan_ExpectedBehavior)
+ {
+ auto bctx = MakeSharedPtr();
+ SharedPtr ctx = StaticCastPtr(bctx);
+ Domain domain("Test");
+ TaskQueueType plan;
+ bctx->Init();
+ SharedPtr task = MakeSharedPtr("Test1");
+ SharedPtr task2 = MakeSharedPtr("Test2");
+ SharedPtr task3 = MakeSharedPtr("Test3");
+
+ SharedPtr subtask4 = MakeSharedPtr("Sub-task4");
+ SharedPtr subtask3 = MakeSharedPtr("Sub-task3");
+ SharedPtr subtask2 = MakeSharedPtr("Sub-task2");
+ SharedPtr subtask1 = MakeSharedPtr("Sub-task1");
+ SharedPtr pausePlan = MakeSharedPtr();
+
+ domain.Add(domain.Root(), task);
+ domain.Add(task, task2);
+ domain.Add(task, subtask4);
+
+ domain.Add(task2, task3);
+ domain.Add(task2, subtask3);
+
+ domain.Add(task3, subtask1);
+ domain.Add(task3, pausePlan);
+ domain.Add(task3, subtask2);
+
+ auto status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Partial);
+ Assert::IsTrue(plan.size() == 1);
+ Assert::AreEqual("Sub-task1"s, plan.front()->Name());
+ Assert::IsTrue(ctx->HasPausedPartialPlan());
+ Assert::IsTrue(ctx->PartialPlanQueue().size() == 2);
+
+ auto theQueue = ctx->PartialPlanQueue();
+
+ ITask* t1ptr = task3.get();
+ ITask* t2ptr = theQueue.front().Task.get();
+ Assert::AreEqual(t1ptr, t2ptr);
+ Assert::AreEqual(2, theQueue.front().TaskIndex);
+
+ theQueue.pop();
+ t1ptr = task.get();
+ t2ptr = theQueue.front().Task.get();
+ Assert::AreEqual(t1ptr, t2ptr);
+ Assert::AreEqual(1, theQueue.front().TaskIndex);
+ }
+ TEST_METHOD(ContinueNestedPausePlan_ExpectedBehavior)
+ {
+ auto bctx = MakeSharedPtr();
+ SharedPtr ctx = StaticCastPtr(bctx);
+ Domain domain("Test");
+ TaskQueueType plan;
+ bctx->Init();
+
+ SharedPtr task = MakeSharedPtr("Test1");
+ SharedPtr task2 = MakeSharedPtr("Test2");
+ SharedPtr task3 = MakeSharedPtr("Test3");
+
+ SharedPtr subtask4 = MakeSharedPtr("Sub-task4");
+ SharedPtr subtask3 = MakeSharedPtr("Sub-task3");
+ SharedPtr subtask2 = MakeSharedPtr("Sub-task2");
+ SharedPtr subtask1 = MakeSharedPtr("Sub-task1");
+ SharedPtr pausePlan = MakeSharedPtr();
+
+ domain.Add(domain.Root(), task);
+ domain.Add(task, task2);
+ domain.Add(task, subtask4);
+
+ domain.Add(task2, task3);
+ domain.Add(task2, subtask3);
+
+ domain.Add(task3, subtask1);
+ domain.Add(task3, pausePlan);
+ domain.Add(task3, subtask2);
+
+ auto status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Partial);
+ Assert::IsTrue(plan.size() == 1);
+ Assert::AreEqual("Sub-task1"s, plan.front()->Name());
+ Assert::IsTrue(ctx->HasPausedPartialPlan());
+ Assert::IsTrue(ctx->PartialPlanQueue().size() == 2);
+
+ PartialPlanQueueType queueCopy = ctx->PartialPlanQueue();
+ ITask* t1ptr = task3.get();
+ ITask* t2ptr = queueCopy.front().Task.get();
+ Assert::AreEqual(t1ptr, t2ptr);
+ Assert::AreEqual(2, queueCopy.front().TaskIndex);
+
+ queueCopy.pop();
+ t1ptr = task.get();
+ t2ptr = queueCopy.front().Task.get();
+ Assert::AreEqual(t1ptr, t2ptr);
+ Assert::AreEqual(1, queueCopy.front().TaskIndex);
+
+ status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Succeeded);
+ Assert::IsTrue(plan.size() == 2);
+ Assert::AreEqual("Sub-task2"s, plan.front()->Name());
+ plan.pop();
+ Assert::AreEqual("Sub-task4"s, plan.front()->Name());
+ }
+ TEST_METHOD(ContinueMultipleNestedPausePlan_ExpectedBehavior)
+ {
+ auto bctx = MakeSharedPtr();
+ SharedPtr ctx = StaticCastPtr(bctx);
+ Domain domain("Test");
+ TaskQueueType plan;
+ bctx->Init();
+
+ SharedPtr task = MakeSharedPtr("Test1");
+ SharedPtr task2 = MakeSharedPtr("Test2");
+ SharedPtr task3 = MakeSharedPtr("Test3");
+ SharedPtr task4 = MakeSharedPtr("Test4");
+
+ domain.Add(domain.Root(), task);
+ SharedPtr subtask1 = MakeSharedPtr("Sub-task1");
+ SharedPtr pausePlan1 = MakeSharedPtr();
+ SharedPtr subtask2 = MakeSharedPtr("Sub-task2");
+ domain.Add(task3, subtask1);
+ domain.Add(task3, pausePlan1);
+ domain.Add(task3, subtask2);
+
+ SharedPtr subtask3 = MakeSharedPtr("Sub-task3");
+ domain.Add(task2, task3);
+ domain.Add(task2, subtask3);
+
+ SharedPtr subtask5 = MakeSharedPtr("Sub-task5");
+ SharedPtr pausePlan2 = MakeSharedPtr();
+ SharedPtr subtask6 = MakeSharedPtr("Sub-task6");
+ domain.Add(task4, subtask5);
+ domain.Add(task4, pausePlan2);
+ domain.Add(task4, subtask6);
+
+ domain.Add(task, task2);
+ SharedPtr subtask4 = MakeSharedPtr("Sub-task4");
+ domain.Add(task, subtask4);
+ domain.Add(task, task4);
+ SharedPtr subtask7 = MakeSharedPtr("Sub-task7");
+ domain.Add(task, subtask7);
+
+ auto status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Partial);
+ Assert::IsTrue(plan.size() == 1);
+ Assert::AreEqual("Sub-task1"s, plan.front()->Name());
+ Assert::IsTrue(ctx->HasPausedPartialPlan());
+ Assert::IsTrue(ctx->PartialPlanQueue().size() == 2);
+
+ PartialPlanQueueType queueCopy = ctx->PartialPlanQueue();
+
+ ITask* t1ptr = task3.get();
+ ITask* t2ptr = queueCopy.front().Task.get();
+ Assert::AreEqual(t1ptr, t2ptr);
+ Assert::AreEqual(2, queueCopy.front().TaskIndex);
+ queueCopy.pop();
+ t1ptr = task.get();
+ Assert::AreEqual(t1ptr, queueCopy.front().Task.get());
+ Assert::AreEqual(1, queueCopy.front().TaskIndex);
+
+ status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Partial);
+ Assert::IsTrue(plan.size() == 3);
+ Assert::AreEqual("Sub-task2"s, plan.front()->Name());
+ plan.pop();
+ Assert::AreEqual("Sub-task4"s, plan.front()->Name());
+ plan.pop();
+ Assert::AreEqual("Sub-task5"s, plan.front()->Name());
+
+ status = domain.FindPlan(*bctx, plan);
+
+ Assert::IsTrue(status == DecompositionStatus::Succeeded);
+ Assert::IsTrue(plan.size() == 2);
+ Assert::AreEqual("Sub-task6"s, plan.front()->Name());
+ plan.pop();
+ Assert::AreEqual("Sub-task7"s, plan.front()->Name());
+ }
+ };
+} // namespace FluidHTNCPPUnitTests
\ No newline at end of file
diff --git a/Fluid-HTNCPP.UnitTests/Fluid-HTNCPP.UnitTests.vcxproj b/Fluid-HTNCPP.UnitTests/Fluid-HTNCPP.UnitTests.vcxproj
new file mode 100644
index 0000000..98322f0
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/Fluid-HTNCPP.UnitTests.vcxproj
@@ -0,0 +1,205 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {402394C5-2D69-49F0-B2E2-239FAB4CC252}
+ Win32Proj
+ FluidHTNCPPUnitTests
+ 10.0
+ NativeUnitTestProject
+
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+ false
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+ false
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+ false
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)$(Configuration)\$(Platform)\Output\Test\
+ $(SolutionDir)$(Configuration)\$(Platform)\Intermediate\Test\
+
+
+ true
+ $(SolutionDir)$(Configuration)\$(Platform)\Output\Test\
+ $(SolutionDir)$(Configuration)\$(Platform)\Intermediate\Test\
+
+
+ false
+ $(SolutionDir)$(Configuration)\$(Platform)\Output\Test\
+ $(SolutionDir)$(Configuration)\$(Platform)\Intermediate\Test\
+
+
+ false
+ $(SolutionDir)$(Configuration)\$(Platform)\Output\Test\
+ $(SolutionDir)$(Configuration)\$(Platform)\Intermediate\Test\
+
+
+
+ Use
+ Level4
+ true
+ $(SolutionDir)Fluid-HTNCPP;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ WIN32;_DEBUG;%(PreprocessorDefinitions)
+ true
+ pch.h
+ true
+ stdcpp17
+
+
+ Windows
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+
+
+
+
+ Use
+ Level4
+ true
+ $(SolutionDir)Fluid-HTNCPP;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ _DEBUG;%(PreprocessorDefinitions)
+ true
+ pch.h
+ true
+ stdcpp17
+
+
+ Windows
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+
+
+
+
+ Use
+ Level4
+ true
+ true
+ true
+ $(SolutionDir)Fluid-HTNCPP;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ WIN32;NDEBUG;%(PreprocessorDefinitions)
+ true
+ pch.h
+ true
+ stdcpp17
+
+
+ Windows
+ true
+ true
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+
+
+
+
+ Use
+ Level4
+ true
+ true
+ true
+ $(SolutionDir)Fluid-HTNCPP;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ NDEBUG;%(PreprocessorDefinitions)
+ true
+ pch.h
+ true
+ stdcpp17
+
+
+ Windows
+ true
+ true
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+
+
+
+
+
+
+
+
+
+
+ Create
+ Create
+ Create
+ Create
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {0c469b65-6d39-4667-8d00-9ec963c518e3}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Fluid-HTNCPP.UnitTests/Fluid-HTNCPP.UnitTests.vcxproj.filters b/Fluid-HTNCPP.UnitTests/Fluid-HTNCPP.UnitTests.vcxproj.filters
new file mode 100644
index 0000000..0953159
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/Fluid-HTNCPP.UnitTests.vcxproj.filters
@@ -0,0 +1,63 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/Fluid-HTNCPP.UnitTests/FuncConditionTests.cpp b/Fluid-HTNCPP.UnitTests/FuncConditionTests.cpp
new file mode 100644
index 0000000..485cf72
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/FuncConditionTests.cpp
@@ -0,0 +1,43 @@
+#include "pch.h"
+#include "CppUnitTest.h"
+#include "Contexts/BaseContext.h"
+#include "Conditions/Condition.h"
+#include "DomainTestContext.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+using namespace FluidHTN;
+
+
+namespace FluidHTNCPPUnitTests
+{
+TEST_CLASS(FuncConditionTests)
+{
+ TEST_METHOD(SetsName_ExpectedBehavior)
+ {
+ auto c = MakeSharedPtr("Name"s, nullptr);
+
+ Assert::AreEqual("Name"s, c->Name());
+ }
+
+ TEST_METHOD(IsValidFailsWithoutFunctionPtr_ExpectedBehavior)
+ {
+ auto ctx = MakeSharedPtr();
+ auto c = MakeSharedPtr("Name"s, nullptr);
+
+ auto result = c->IsValid(*ctx);
+
+ Assert::AreEqual(false, result);
+ }
+
+ TEST_METHOD(IsValidCallsInternalFunctionPtr_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ auto c = MakeSharedPtr("Name"s,
+ [](IContext& ctx) { return (static_cast(ctx).Done() == false); });
+ auto result = c->IsValid(ctx);
+
+ Assert::AreEqual(true, result);
+ }
+} ;
+} // namespace FluidHTNCPPUnitTests
diff --git a/Fluid-HTNCPP.UnitTests/FuncOperatorTests.cpp b/Fluid-HTNCPP.UnitTests/FuncOperatorTests.cpp
new file mode 100644
index 0000000..c9cd75f
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/FuncOperatorTests.cpp
@@ -0,0 +1,52 @@
+#include "pch.h"
+#include "CppUnitTest.h"
+#include "Contexts/BaseContext.h"
+#include "Operators/Operator.h"
+#include "DomainTestContext.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+using namespace FluidHTN;
+
+
+
+namespace FluidHTNCPPUnitTests
+{
+TEST_CLASS(FuncOperatorTests)
+{
+ TEST_METHOD(UpdateDoesNothingWithoutFunctionPtr_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ auto e = MakeSharedPtr(nullptr, nullptr);
+ e->Update(ctx);
+ }
+
+TEST_METHOD(StopDoesNothingWithoutFunctionPtr_ExpectedBehavior)
+{
+ DomainTestContext ctx;
+ auto e = MakeSharedPtr(nullptr, nullptr);
+
+ e->Stop(ctx);
+}
+TEST_METHOD(UpdateReturnsStatusInternalFunctionPtr_ExpectedBehavior)
+{
+ DomainTestContext ctx;
+ auto e = MakeSharedPtr([=](IContext&) { return TaskStatus::Success; }, nullptr);
+
+ auto status = e->Update(ctx);
+
+ Assert::AreEqual((int)TaskStatus::Success, (int)status);
+}
+
+TEST_METHOD(StopCallsInternalFunctionPtr_ExpectedBehavior)
+{
+ DomainTestContext ctx;
+ auto e = MakeSharedPtr(nullptr, [](IContext&ctx ) { static_cast(ctx).Done() = true; });
+
+ e->Stop(ctx);
+
+ Assert::AreEqual(true, ctx.Done());
+}
+}
+;
+} // namespace FluidHTNCPPUnitTests
diff --git a/Fluid-HTNCPP.UnitTests/PlannerTests.cpp b/Fluid-HTNCPP.UnitTests/PlannerTests.cpp
new file mode 100644
index 0000000..785647e
--- /dev/null
+++ b/Fluid-HTNCPP.UnitTests/PlannerTests.cpp
@@ -0,0 +1,461 @@
+#include "pch.h"
+#include "CppUnitTest.h"
+#include "Contexts/BaseContext.h"
+#include "CoreIncludes/Domain.h"
+#include "Planners/Planner.h"
+#include "Tasks/CompoundTasks/Selector.h"
+#include "Tasks/PrimitiveTasks/PrimitiveTask.h"
+#include "DomainTestContext.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+using namespace FluidHTN;
+
+namespace FluidHTNCPPUnitTests
+{
+ TEST_CLASS(PlannerTests)
+ {
+ TEST_METHOD(GetPlanReturnsClearInstanceAtStart_ExpectedBehavior)
+ {
+ Planner planner;
+ auto plan = planner.GetPlan();
+
+ Assert::IsTrue(plan.size() == 0);
+ }
+ TEST_METHOD(GetCurrentTaskReturnsNullAtStart_ExpectedBehavior)
+ {
+ Planner planner;
+ auto task = planner.GetCurrentTask();
+
+ Assert::IsTrue(task == nullptr);
+ }
+ TEST_METHOD(TickWithoutInitializedContextThrowsException_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ Domain domain("Test"s);
+ Planner planner;
+ Assert::ExpectException([&]() { planner.Tick(domain, ctx); });
+ }
+ TEST_METHOD(TickWithEmptyDomain_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ Domain domain("Test"s);
+ Planner planner;
+ ctx.Init();
+ planner.Tick(domain, ctx);
+ }
+
+ TEST_METHOD(TickWithPrimitiveTaskWithoutOperator_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ Domain domain("Test"s);
+ Planner planner;
+ ctx.Init();
+ SharedPtr task1 = MakeSharedPtr("Test"s);
+ SharedPtr task2 = MakeSharedPtr("Sub-task");
+
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+
+ planner.Tick(domain, ctx);
+ auto currentTask = planner.GetCurrentTask();
+
+ Assert::IsTrue(currentTask == nullptr);
+ Assert::IsTrue(planner.LastStatus() == TaskStatus::Failure);
+ }
+
+ TEST_METHOD(TickWithFuncOperatorWithNullFunc_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ Domain domain("Test"s);
+ Planner planner;
+ ctx.Init();
+
+ SharedPtr task1 = MakeSharedPtr("Test"s);
+ SharedPtr task2 = MakeSharedPtr("Sub-task");
+ SharedPtr f = MakeSharedPtr(nullptr);
+
+ task2->SetOperator(f);
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+
+ planner.Tick(domain, ctx);
+ auto currentTask = planner.GetCurrentTask();
+
+ Assert::IsTrue(currentTask == nullptr);
+ Assert::IsTrue(planner.LastStatus() == TaskStatus::Failure);
+ }
+ TEST_METHOD(TickWithDefaultSuccessOperatorWontStackOverflows_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ Domain domain("Test"s);
+ Planner planner;
+ ctx.Init();
+ SharedPtr task1 = MakeSharedPtr("Test"s);
+ SharedPtr task2 = MakeSharedPtr("Sub-task");
+ SharedPtr f = MakeSharedPtr([](IContext& ) { return TaskStatus::Success; });
+ task2->SetOperator(f);
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+
+ planner.Tick(domain, ctx);
+ auto currentTask = planner.GetCurrentTask();
+
+ Assert::IsTrue(currentTask == nullptr);
+ Assert::IsTrue(planner.LastStatus() == TaskStatus::Success);
+ }
+
+ TEST_METHOD(TickWithDefaultContinueOperator_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ Domain domain("Test"s);
+ Planner planner;
+ ctx.Init();
+ SharedPtr task1 = MakeSharedPtr("Test"s);
+ SharedPtr task2 = MakeSharedPtr("Sub-task");
+ SharedPtr f = MakeSharedPtr([](IContext& ) { return TaskStatus::Continue; });
+
+ task2->SetOperator(f);
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+
+ planner.Tick(domain, ctx);
+ auto currentTask = planner.GetCurrentTask();
+
+ Assert::IsTrue(currentTask != nullptr);
+ Assert::IsTrue(planner.LastStatus() == TaskStatus::Continue);
+ }
+
+ TEST_METHOD(OnNewPlan_ExpectedBehavior)
+ {
+ DomainTestContext ctx;
+ Domain domain("Test"s);
+ Planner planner;
+ bool test = false;
+ ctx.Init();
+ planner.OnNewPlan = [&](TaskQueueType p) { test = (p.size() == 1); };
+ SharedPtr task1 = MakeSharedPtr("Test"s);
+ SharedPtr task2 = MakeSharedPtr("Sub-task");
+ SharedPtr f = MakeSharedPtr([](IContext&) { return TaskStatus::Continue; });
+ task2->SetOperator(f);
+ domain.Add(domain.Root(), task1);
+ domain.Add(task1, task2);
+
+ planner.Tick(domain, ctx);
+
+ Assert::IsTrue(test);
+ }
+ TEST_METHOD(OnReplacePlan_ExpectedBehavior)
+ {
+ bool test = false;
+ Domain domain("Test"s);
+ DomainTestContext ctx;
+ Planner planner;
+ ctx.Init();
+ planner.OnReplacePlan = [&](TaskQueueType op, SharedPtr ct, TaskQueueType p) {
+ test = ((op.size() == 0) && (ct != nullptr) && (p.size() == 1));
+ };
+ SharedPtr task1 = MakeSharedPtr("Test1"s);
+ SharedPtr task2 = MakeSharedPtr("Test2"s);
+ SharedPtr task3 = MakeSharedPtr("Sub-task1");
+
+ SharedPtr c = MakeSharedPtr("TestCondition"s, [](IContext& ctx) {
+ return (static_cast(ctx).Done() == false);
+ });
+ task3->AddCondition(c);
+ SharedPtr