diff --git a/.gitignore b/.gitignore index 37d87e5..f580354 100644 --- a/.gitignore +++ b/.gitignore @@ -27,13 +27,11 @@ *.out *.app -# Codeblocks folder ignores +# Build folder ignores bin/ obj/ lib/ -# Codeblocks file ignores -*.layout # Ignore documentation (can be autogenerated via doxygen) docs/ @@ -41,8 +39,17 @@ docs/ # Ignore anything with the LOCAL prefix LOCAL* +# +# My Workflow Ignores +# + +# Codeblocks file ignores +*.layout + # Ignore sublime workspace *.sublime-workspace CppCheckResults.xml -ValgrindOut.xml \ No newline at end of file +ValgrindOut.xml + +*.ycm_extra_conf.py \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..057f41f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "thirdParty/flatbuffers"] + path = thirdParty/flatbuffers + url = https://github.com/google/flatbuffers.git +[submodule "thirdParty/Catch"] + path = thirdParty/Catch + url = https://github.com/philsquared/Catch.git diff --git a/Jamrules b/Jamrules index b0a3d7f..9f8561d 100644 --- a/Jamrules +++ b/Jamrules @@ -1,11 +1,34 @@ + +## +## Compiler +## + CC = gcc ; -C++ = g++ ; -LINK = g++ ; + +# GCC +#C++ = g++ ; +#LINK = g++ ; + +# Clang +C++ = clang++ ; +LINK = clang++ ; + +## +## Compiler arguments +## # Galavant requires C++11 -# fPIC = position independent code. This is so the lib can be linked in other libs/executables -C++FLAGS = -std=c++11 -fPIC -g ; -OBJECTC++FLAGS = -std=c++11 ; +# fPIC = position independent code. This is so the lib can be linked in other libs/executables +# g = debug symbols +# lstdc++ = standard library +# -stdlib=lib++ = Unreal uses libc++, which is busted on Ubuntu 16.04. Remove this option +# if you don't give a shit about Unreal and/or want to build the tests +# Og = compile without optimizations for debugging +# -Wall -Wextra = Error detection/tolerance +#C++FLAGS = -std=c++11 -stdlib=libc++ -fPIC -g -Og -Wall -Wextra ; # Required arguments for Unreal +C++FLAGS = -std=c++11 -fPIC -g -Og -Wall -Wextra ; # Required arguments for tests + +OBJECTC++FLAGS = -std=c++11 -lstdc++ -g -Og ; HDRS = thirdParty/flatbuffers/include ; diff --git a/README.md b/README.md index 779629b..f755026 100644 --- a/README.md +++ b/README.md @@ -7,26 +7,37 @@ With my current design, agents are based on Maslow's hierarchy of needs. This ma Galavant is basically [Horizon](http://github.com/makuto/horizon) v2. I coded myself into a bit of a quagmire in Horizon, so I decided I would be more productive if I started over. -Interested? ------------- -Message me on Github or email me at macoymadson@gmail.com for any questions. I'd love some help! +## Building -There is a lot to the design of Galavant that does not fit in this readme. If you email me, I can fill you in on my goals with Galavant and what the game will actually be like. +Galavant uses Flatbuffers. Before trying to build, make sure to initialize it: +`git submodule update --init --recursive` +Then run cmake in `thirdParty/flatbuffers`: +`cmake .` +Then +`make` -License --------- -The code is MIT licensed. I intend on keeping all data (images, sprites, gameplay/design data) private, following the Doom/Quake model, but for now, consider those to be MIT Licensed. +Jam is used as the build system for Galavant. I used to use makefiles in the Horizon iteration of this project, and I hated them. Jam is much easier to use and takes much less work to get a project set up. If people actually become interested in this project, I can invest some time in cmake-ifying it for more standard building. If you're willing to try building using Jam, you may have to edit Jamrules to set your preferred compiler (the default is g++). -Technical ----------- -I am writing Galavant in C++ (C++11, to be specific). I rely on SFML2 for sound, graphics, input, and networking. SFML is wrapped in my personal game library, [Base2.0](https://github.com/makuto/base2.0) (also MIT licensed), which also has other useful game-agnostic code. +To build with jam, first install jam: +`sudo apt-get install jam` +Then run jam (in galavant/ is fine): +`jam` -Jam is used as the build system for Galavant. I used to use makefiles in the Horizon iteration of this project, and I hated them. Jam is much easier to use and takes much less work to get a project set up. If people actually become interested in this project, I can invest some time in cmake-ifying it for more standard building. If you're willing to try building using jam, you may have to edit Jamrules to set your preferred compiler. +## Dependencies -Dependencies --------------- Galavant uses features of C++11. The following libraries are required by Galavant and included in /thirdParty: - [OpenSimplexNoise](https://gist.github.com/tombsar/716134ec71d1b8c1b530), created by Arthur Tombs (public domain) -- [Flatbuffers](https://github.com/google/flatbuffers), created by Google/Fun Propulsion Labs (Apache License, v2.0) \ No newline at end of file +- [Flatbuffers](https://github.com/google/flatbuffers), created by Google/Fun Propulsion Labs (Apache License, v2.0) +- [Catch](https://github.com/philsquared/Catch), created by various contributors/a dude named Travis (Boost Software License) + +## License + +The code is MIT licensed. I intend on keeping all data (images, sprites, gameplay/design data) private, following the Doom/Quake model, but for now, consider those to be MIT Licensed. + +## Want to Get Involved? + +Message me on Github or email me at macoymadson@gmail.com for any questions. I'd love some help! + +There is a lot to the design of Galavant that does not fit in this readme. If you email me, I can fill you in on my goals with Galavant and what the game will actually be like. \ No newline at end of file diff --git a/assets/designDocs/technical/behaviorTrees.txt b/assets/designDocs/technical/behaviorTrees.txt new file mode 100644 index 0000000..89edb33 --- /dev/null +++ b/assets/designDocs/technical/behaviorTrees.txt @@ -0,0 +1,34 @@ + +Behavior Trees +==================== +- Leaf: Cannot have children + - Conditional: returns success or failure immediately + - Action: can return running +- Composite: Has one or more children + - Parallel: runs multiple children at once + - Sequence: runs children in order, aborts on child failure + - Selector: runs children in order, continues on child failure + - Random: runs one of its children at random +- Decorator: Has exactly one child + - Inverter + - Succeeder: always return success + - Failer: always return failure + - Repeat X times + - Repeat until failure + - Repeat until success + + +Behavior Tree + ReceiveStatuses(Node, (TreeInstances, Statuses)) + + RunChildren(TreeInstances, ChildNodes) + + Update + loopCounter + do + update trees which have nodes which aren't running + + + while there are trees which aren't running and not at max number loops + + tick running nodes \ No newline at end of file diff --git a/assets/designDocs/technical/hierarchicalTaskNetworks.txt b/assets/designDocs/technical/hierarchicalTaskNetworks.txt new file mode 100644 index 0000000..b59f2bf --- /dev/null +++ b/assets/designDocs/technical/hierarchicalTaskNetworks.txt @@ -0,0 +1,82 @@ +Tasks + Something to be done which can be broken down into a method to accomplish (task sort of equals goal) + +Methods + If the agent's state matches a set of preconditions, return a list of methods and primitives which complete the task + +Primitives/Operators + If the agent's state matches a set of preconditions, return that the primitive can be performed + Perform the actual change in the world + Change the world state (this can be both during planning and after actually performing the primitive task) (should + these be separated?) + +agent = entityID +agentState = struct +{ + isUndead = false + hasAmmo = false + target = entityID (should this in agent state or parameters?) +} + +worldState = struct +{ + towns = {townA: [x, y, z, properties], townB: [x, y, z, properties]} + canUndeadDoShit = true +} + +parameters = vector of param +param = struct +{ +enum type +union +{ + entityID entity + coordinate coord + + scalar val + vector vec + float floatVal +} +} + +MethodA = 0 +MethodB = 1 + +PrimitiveA = 0 +PrimitiveB = 1 + +MethodA = struct +{ + preconditionsSatisfied(agent, agentState, worldState, parameters) + { + return worldState.useMethodA + } + newTasks = [(1, method, [param1, param2]), (0, primitive, [])]; +} + +PrimitiveA = module +{ + arePreconditionsSatisfied(agent, agentState, worldState, parameters) + { + return worldState.useMethodA + } + + applyEffects(agent, agentState, worldState, parameters) + { + worldState.hasDonePrimitiveA = true + } + + execute(agent, agentState, worldState, parameters) + { + if preconditionsSatisfied(agent, agentState, worldState, parameters) + primitiveAComponentManager.subscribeTaskAgent(agent, this) // pass self in for callback (instead: event listener?) + return true + return false + } + + PrimitiveADoneCallback(agent, bool succeeded) + { + // plan follow code will call applyEffects ? + notifyPlanFollowCode(agent, succeeded) + } +} \ No newline at end of file diff --git a/src/GalavantMain.cpp b/src/GalavantMain.cpp index 6289aa9..dc0cbec 100644 --- a/src/GalavantMain.cpp +++ b/src/GalavantMain.cpp @@ -4,5 +4,5 @@ void gv::GalavantMain::Update(float frameTime) { - std::cout << "Updated Galavant\n"; + std::cout << "Updated Galavant frameTime: " << frameTime << "\n"; } \ No newline at end of file diff --git a/src/Jamfile b/src/Jamfile index efe5ed3..7ee27ca 100644 --- a/src/Jamfile +++ b/src/Jamfile @@ -8,6 +8,8 @@ MakeLocate libGalavant.a : lib ; SubInclude . src thirdPartyWrapper ; SubInclude . src entityComponentSystem ; +SubInclude . src ai ; +SubInclude . src world ; # Experiments and Testing (feel free to remove these if you don't want them built) SubInclude . src experiments ; diff --git a/src/ai/Jamfile b/src/ai/Jamfile new file mode 100644 index 0000000..af71ca8 --- /dev/null +++ b/src/ai/Jamfile @@ -0,0 +1,5 @@ +SubDir . src ai ; + +Library libGalaAi : htn/HTNTasks.cpp htn/HTNPlanner.cpp ; + +MakeLocate libGalaAi.a : lib ; \ No newline at end of file diff --git a/src/ai/htn/HTNPlanner.cpp b/src/ai/htn/HTNPlanner.cpp new file mode 100644 index 0000000..347ad49 --- /dev/null +++ b/src/ai/htn/HTNPlanner.cpp @@ -0,0 +1,416 @@ +#include "HTNPlanner.hpp" + +#include "HTNTypes.hpp" +#include "HTNTasks.hpp" + +#include + +namespace Htn +{ +bool DecomposeGoalTask(GoalDecompositionStack& decompositionStack, GoalTask* goalTask, + int methodIndex, ParameterList& parameters, WorldState& state) +{ + GoalDecomposition decomposition; + decomposition.DecomposedGoalTask = goalTask; + decomposition.MethodIndex = methodIndex; + decomposition.Parameters = parameters; + decomposition.InitialState = state; + + // Perform the decomposition + if (goalTask->DecomposeMethodAtIndex(decomposition.CallList, decomposition.MethodIndex, + decomposition.Parameters)) + { + decompositionStack.push_back(decomposition); + return true; + } + + return false; +} + +bool DecomposeCompoundTask(TaskCallList& compoundDecompositions, CompoundTask* compoundTask, + const WorldState& state, const ParameterList& parameters) +{ + if (!compoundTask->StateMeetsPreconditions(state, parameters)) + return false; + + return compoundTask->Decompose(compoundDecompositions, state, parameters); +} + +// When the stack is empty, find a goal task to push onto the task or add tasks as per usual +// If any Decompositions or Preconditions fail, we must fail the entire plan because we have +// no alternate methods +Planner::Status Planner::PlanStep_BottomLevel(void) +{ + if (InitialCallList.empty()) + return Status::Failed_NoTasks; + else if (WorkingCallList.empty() && FinalCallList.empty()) + { + // First time the planner has been stepped + WorkingCallList = InitialCallList; + StacklessState = State; + } + else if (WorkingCallList.empty() && !FinalCallList.empty()) + return Status::PlanComplete; + + TaskCallList compoundDecompositions; + + if (DebugPrint) + { + std::cout << "\nPlanStep()\nWorkingCallList.size() = " << WorkingCallList.size() << "\n"; + printTaskCallList(WorkingCallList); + } + + for (TaskCallListIterator currentTaskCallIter = WorkingCallList.begin(); + currentTaskCallIter != WorkingCallList.end(); + currentTaskCallIter = WorkingCallList.erase(currentTaskCallIter)) + { + TaskCall currentTaskCall = (*currentTaskCallIter); + Task* currentTask = currentTaskCall.TaskToCall; + if (!currentTask) + continue; + TaskType currentTaskType = currentTask->GetType(); + + if (DebugPrint) + std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n"; + + switch (currentTaskType) + { + case TaskType::Goal: + { + if (DebugPrint) + std::cout << "Goal\n"; + GoalTask* goalTask = currentTask->GetGoal(); + + if (!DecomposeGoalTask(DecompositionStack, goalTask, 0, currentTaskCall.Parameters, + StacklessState)) + return Status::Failed_NoPossiblePlan; + + WorkingCallList.erase(currentTaskCallIter); + return Status::Running_SuccessfulDecompositionStackPush; + } + break; + case TaskType::Primitive: + { + if (DebugPrint) + std::cout << "Primitive\n"; + PrimitiveTask* primitiveTask = currentTask->GetPrimitive(); + if (!primitiveTask->StateMeetsPreconditions(StacklessState, + currentTaskCall.Parameters)) + return Status::Failed_NoPossiblePlan; + + // If at top level, apply primitive tasks immediately to the stackless state + primitiveTask->ApplyStateChange(StacklessState, currentTaskCall.Parameters); + + FinalCallList.push_back(currentTaskCall); + + if (BreakOnPrimitiveApply) + { + WorkingCallList.erase(currentTaskCallIter); + return Status::Running_SuccessfulPrimitive; + } + // Keep processing tasks otherwise + } + break; + case TaskType::Compound: + { + if (DebugPrint) + std::cout << "Compound\n"; + CompoundTask* compoundTask = currentTask->GetCompound(); + + // we need to push our decomposition to our call list, but we're iterating on + // it. By pushing our decomposition to compoundDecompositions in + // DecomposeCompoundTask(), we'll then break out of the loop and tack it on there + if (!DecomposeCompoundTask(compoundDecompositions, compoundTask, StacklessState, + currentTaskCall.Parameters)) + return Status::Failed_NoPossiblePlan; + + currentTaskCallIter = WorkingCallList.erase(currentTaskCallIter); + } + break; + default: + return Status::Failed_BadData; + } + + if (compoundDecompositions.size()) + { + if (DebugPrint) + { + std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size() + << "\n"; + } + WorkingCallList.insert(WorkingCallList.begin(), compoundDecompositions.begin(), + compoundDecompositions.end()); + + if (DebugPrint) + std::cout << "PlanStep Done\n"; + + // We have to break here because we are adding things to the list we're iterating + // on; We'll process the tasks next Step + return Status::Running_SuccessfulDecomposition; + } + + if (DebugPrint) + std::cout << "Loop Done\n"; + } + + return Status::PlanComplete; +} + +Planner::Status Planner::PlanStep_StackFrame(void) +{ + // Remember: If goal fails to decompose and goal is bottom of stack, fail + GoalDecompositionStack::iterator currentStackFrameIter = DecompositionStack.end() - 1; + GoalDecomposition& currentStackFrame = *currentStackFrameIter; + + if (DebugPrint) + { + std::cout << "\nPlanStep()\ncurrentStackFrame.CallList.size() = " + << currentStackFrame.CallList.size() << "\n"; + printTaskCallList(currentStackFrame.CallList); + + std::cout << "Stack Depth: "; + for (unsigned int i = 0; i < DecompositionStack.size(); i++) + std::cout << "="; + std::cout << "\n"; + + { + std::cout << "----Fullstack working lists\n"; + std::cout << "[0]\n"; + printTaskCallList(WorkingCallList); + int i = 1; + for (GoalDecomposition& stackFrame : DecompositionStack) + { + std::cout << "[" << i++ << "]\n"; + printTaskCallList(stackFrame.CallList); + } + std::cout << "----\n"; + } + } + + TaskCallList compoundDecompositions; + bool methodFailed = false; + + for (TaskCallListIterator currentTaskCallIter = currentStackFrame.CallList.begin(); + currentTaskCallIter != currentStackFrame.CallList.end(); + currentTaskCallIter = currentStackFrame.CallList.erase(currentTaskCallIter)) + { + TaskCall currentTaskCall = (*currentTaskCallIter); + Task* currentTask = currentTaskCall.TaskToCall; + if (!currentTask) + continue; + TaskType currentTaskType = currentTask->GetType(); + + if (DebugPrint) + std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n"; + + switch (currentTaskType) + { + case TaskType::Goal: + { + if (DebugPrint) + std::cout << "Goal\n"; + GoalTask* goalTask = currentTask->GetGoal(); + + // TODO erase ahead of time because fuck + // Strange things are afoot when we push to stack + currentStackFrame.CallList.erase(currentTaskCallIter); + + // Perform the decomposition. This function only fails if the method at method + // index doesn't exist. This means that the goal task has no alternative options + if (!DecomposeGoalTask(DecompositionStack, goalTask, 0, currentTaskCall.Parameters, + currentStackFrame.WorkingState)) + { + methodFailed = true; + break; + } + + // This code is wrong. I'm not sure why. + // Pushing to the stack invalidates our currentStackFrame reference; update it + // What the actual fuck (updating the ref doesn't fix the fucking thing) + /*std::cout << "Ref updating from " << ¤tStackFrame << " to " + << &(*(DecompositionStack.end() - 1)) << "\n"; + currentStackFrameIter = DecompositionStack.end() - 1; + currentStackFrame = *currentStackFrameIter;*/ + + return Status::Running_SuccessfulDecompositionStackPush; + } + break; + case TaskType::Primitive: + { + if (DebugPrint) + std::cout << "Primitive\n"; + PrimitiveTask* primitiveTask = currentTask->GetPrimitive(); + if (!primitiveTask->StateMeetsPreconditions(currentStackFrame.WorkingState, + currentTaskCall.Parameters)) + { + methodFailed = true; + break; + } + + primitiveTask->ApplyStateChange(currentStackFrame.WorkingState, + currentTaskCall.Parameters); + + currentStackFrame.FinalCallList.push_back(currentTaskCall); + + if (BreakOnPrimitiveApply) + { + currentStackFrame.CallList.erase(currentTaskCallIter); + return Status::Running_SuccessfulPrimitive; + } + // Keep processing tasks otherwise + } + break; + case TaskType::Compound: + { + if (DebugPrint) + std::cout << "Compound\n"; + CompoundTask* compoundTask = currentTask->GetCompound(); + + if (!DecomposeCompoundTask(compoundDecompositions, compoundTask, + currentStackFrame.WorkingState, + currentTaskCall.Parameters)) + { + methodFailed = true; + break; + } + + currentTaskCallIter = currentStackFrame.CallList.erase(currentTaskCallIter); + + // we need to push our decomposition to our call list, but we're iterating on + // it. By pushing our decomposition to compoundDecompositions, we break out of + // the loop and tack it on there + } + break; + default: + return Status::Failed_BadData; + } + + if (methodFailed) + { + if (DebugPrint) + std::cout << "Method failed decomposition\n"; + // Clear stack frame + currentStackFrame.CallList.clear(); + currentStackFrame.FinalCallList.clear(); + currentStackFrame.WorkingState = currentStackFrame.InitialState; + + // Try the next method + currentStackFrame.MethodIndex++; + if (!currentStackFrame.DecomposedGoalTask->DecomposeMethodAtIndex( + currentStackFrame.CallList, currentStackFrame.MethodIndex, + currentStackFrame.Parameters)) + { + DecompositionStack.pop_back(); + return Status::Running_FailedGoalDecomposition; + } + + // We have failed to decompose the previous method, but successfully got the next + // method. Return to let the Planner know what just happened + return Status::Running_FailedMethodDecomposition; + } + + if (compoundDecompositions.size()) + { + if (DebugPrint) + { + std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size() + << "\n"; + std::cout << "currentStackFrame.CallList.size() = " + << currentStackFrame.CallList.size() << "\n"; + std::cout << "Decomposition:\n"; + printTaskCallList(compoundDecompositions); + } + currentStackFrame.CallList.insert(currentStackFrame.CallList.begin(), + compoundDecompositions.begin(), + compoundDecompositions.end()); + if (DebugPrint) + std::cout << "PlanStep Done\n"; + + // We have to break here because we are adding things to the list we're iterating + // on; We'll process the tasks next Step + return Status::Running_SuccessfulDecomposition; + } + + if (DebugPrint) + std::cout << "Loop Done\n"; + } + + // Finished processing this stack frame + if (currentStackFrame.CallList.empty() && !currentStackFrame.FinalCallList.empty()) + { + bool onlyStackFrame = DecompositionStack.size() == 1; + TaskCallList* parentFinalCallList = nullptr; + WorldState* parentWorkingState = nullptr; + + // If this is the only frame on the stack, its call list and working state goes to the + // plan, else, the previous stack + if (onlyStackFrame) + { + parentFinalCallList = &FinalCallList; + parentWorkingState = &StacklessState; + } + else + { + GoalDecomposition& previousStackFrame = *(currentStackFrameIter - 1); + + parentFinalCallList = &previousStackFrame.FinalCallList; + parentWorkingState = &previousStackFrame.WorkingState; + } + + if (DebugPrint) + { + std::cout << "Collapsing stack frame. Adding List:\n"; + printTaskCallList(currentStackFrame.FinalCallList); + std::cout << "To parent:\n"; + printTaskCallList(*parentFinalCallList); + } + + parentFinalCallList->insert(parentFinalCallList->end(), + currentStackFrame.FinalCallList.begin(), + currentStackFrame.FinalCallList.end()); + + *parentWorkingState = currentStackFrame.WorkingState; + + DecompositionStack.pop_back(); + + if (DebugPrint) + std::cout << "Frame Done\n"; + + return Status::Running_SuccessfulDecompositionStackPop; + } + + return Status::PlanComplete; +} + +// TODO: Pool various task lists? +// TODO: Pull more things out into functions, if possible. It's bad that whenever I make a change to +// something I have to change it in two places +Planner::Status Planner::PlanStep(void) +{ + Status status = Status::Failed_NoPossiblePlan; + + // Continue stepping the plan until a non-running state is complete or a status is returned + // which matches the user-specified break conditions + do + { + if (DecompositionStack.empty()) + status = PlanStep_BottomLevel(); + else + status = PlanStep_StackFrame(); + + // The user can decided how much planning happens in a single step; conditionally stop + // stepping here based on their settings + if ((BreakOnCompoundDecomposition && status == Status::Running_SuccessfulDecomposition) || + (BreakOnPrimitiveApply && status == Status::Running_SuccessfulPrimitive) || + (BreakOnStackAction && (status == Status::Running_SuccessfulDecompositionStackPush || + status == Status::Running_SuccessfulDecompositionStackPop || + status == Status::Running_FailedGoalDecomposition)) || + (BreakOnFailedDecomposition && (status == Status::Running_FailedGoalDecomposition || + status == Status::Running_FailedMethodDecomposition))) + return status; + + } while (status >= Status::Running_EnumBegin && status <= Status::Running_EnumEnd); + + return status; +} +} \ No newline at end of file diff --git a/src/ai/htn/HTNPlanner.hpp b/src/ai/htn/HTNPlanner.hpp new file mode 100644 index 0000000..53c44d4 --- /dev/null +++ b/src/ai/htn/HTNPlanner.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include "HTNTypes.hpp" +#include "HTNTasks.hpp" + +namespace Htn +{ +struct GoalDecomposition +{ + // The GoalTask this decomposition came from + GoalTask* DecomposedGoalTask; + + // The index to the Method used for the current decomposition + int MethodIndex; + + // The tasks themselves created by the Method + TaskCallList CallList; + + // The state and parameters at the time this decomposition took place + WorldState InitialState; + ParameterList Parameters; + + // State while the method is being decomposed + WorldState WorkingState; + + // The result of this stack frame decomposition (only primitive tasks remaining) + TaskCallList FinalCallList; +}; + +typedef std::vector GoalDecompositionStack; + +// TODO: Either make planner allow making multiple plans with the same instance, or make it clear +// that it is for a single plan only +class Planner +{ +public: + WorldState State; + + TaskCallList InitialCallList; + + // Filled out by PlanStep(); Should only be used once PlanStep returns PlanComplete + TaskCallList FinalCallList; + + // + // Settings to tweak how long you want a single PlanStep() to be + // + // Break whenever a stack action has occurred (a goal has been decomposed, a method has finished + // decomposition, or a method failed to decompose) + bool BreakOnStackAction = true; + // Break immediately after a compound task has been decomposed + bool BreakOnCompoundDecomposition = false; + // Break immediately after a primitive task has been applied + bool BreakOnPrimitiveApply = false; + // Break whenever a goal or method failed to decompose + bool BreakOnFailedDecomposition = false; + + // Print various details about the status of the stack etc. + bool DebugPrint = false; + + enum class Status + { + // Bad + Failed_BadData = 0, + Failed_NoPossiblePlan, + Failed_NoTasks, + + Running_EnumBegin, + + Running_SuccessfulDecomposition, + Running_SuccessfulDecompositionStackPush, + Running_SuccessfulDecompositionStackPop, + Running_SuccessfulPrimitive, + Running_FailedMethodDecomposition, + Running_FailedGoalDecomposition, + + Running_EnumEnd, + + // Done + PlanComplete + }; + + Status PlanStep(void); + +private: + GoalDecompositionStack DecompositionStack; + + // Used for when all goals have been decomposed to manage mutable state + WorldState StacklessState; + + // Copy of InitialCallList that Planner can fuck with + TaskCallList WorkingCallList; + + Status PlanStep_StackFrame(void); + Status PlanStep_BottomLevel(void); +}; +}; \ No newline at end of file diff --git a/src/ai/htn/HTNTasks.cpp b/src/ai/htn/HTNTasks.cpp new file mode 100644 index 0000000..5d94eb9 --- /dev/null +++ b/src/ai/htn/HTNTasks.cpp @@ -0,0 +1,114 @@ +#include "HTNTasks.hpp" + +#include +#include + +namespace Htn +{ +int GoalTask::GetNumMethods(void) +{ + return Methods ? Methods->size() : 0; +} + +bool GoalTask::DecomposeMethodAtIndex(TaskCallList& decomposition, int index, + const ParameterList& parameters) +{ + Task* methodToDecompose = GetMethodAtIndex(index); + if (!methodToDecompose) + return false; + + // TODO Fix this is screwy + decomposition.push_back({methodToDecompose, parameters}); + return true; +} + +Task* GoalTask::GetMethodAtIndex(int index) +{ + if (index < 0 || index >= GetNumMethods() || !Methods) + return nullptr; + + return (*Methods)[index]; +} + +void GoalTask::SetMethods(TaskList* newMethods) +{ + Methods = newMethods; +} + +Task::Task(GoalTask* goal) +{ + Goal = goal; + Type = TaskType::Goal; +} + +Task::Task(CompoundTask* compound) +{ + Compound = compound; + Type = TaskType::Compound; +} + +Task::Task(PrimitiveTask* primitive) +{ + Primitive = primitive; + Type = TaskType::Primitive; +} + +TaskType Task::GetType(void) const +{ + return Type; +} + +GoalTask* Task::GetGoal(void) +{ + assert(Type == TaskType::Goal); + return Goal; +} +CompoundTask* Task::GetCompound(void) +{ + assert(Type == TaskType::Compound); + return Compound; +} +PrimitiveTask* Task::GetPrimitive(void) +{ + assert(Type == TaskType::Primitive); + return Primitive; +} + +std::ostream& operator<<(std::ostream& os, const Task& task) +{ + switch (task.GetType()) + { + case TaskType::None: + os << "Task Type:None " << &task; + break; + case TaskType::Goal: + os << "Task Type:Goal task addr " << &task << " Goal addr " << task.Goal; + break; + case TaskType::Compound: + os << "Task Type:Compound addr " << &task << " Compound addr " << task.Compound; + break; + case TaskType::Primitive: + os << "Task Type:Primitive addr " << &task << " Primitive addr " << task.Primitive; + break; + } + return os; +} + +void printTaskList(const TaskList& tasks) +{ + std::cout << "TaskList size = " << tasks.size() << " addr " << &tasks << ":\n"; + for (unsigned int i = 0; i < tasks.size(); i++) + { + std::cout << "\t[" << i << "] " << *tasks[i] << "\n"; + } +} + +void printTaskCallList(const TaskCallList& tasks) +{ + std::cout << "TaskCallList size = " << tasks.size() << " addr " << &tasks << ":\n"; + for (unsigned int i = 0; i < tasks.size(); i++) + { + std::cout << "\t[" << i << "] " << *tasks[i].TaskToCall << "\n"; + } +} +} \ No newline at end of file diff --git a/src/ai/htn/HTNTasks.hpp b/src/ai/htn/HTNTasks.hpp new file mode 100644 index 0000000..36f4a36 --- /dev/null +++ b/src/ai/htn/HTNTasks.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include "HTNTypes.hpp" + +#include + +namespace Htn +{ +struct Task; +typedef std::vector TaskList; + +struct TaskCall +{ + Task* TaskToCall; + ParameterList Parameters; +}; + +typedef std::vector TaskCallList; +typedef TaskCallList::iterator TaskCallListIterator; + +// +// Tasks +// + +class GoalTask +{ +private: + TaskList* Methods; + +public: + GoalTask(void) = default; + + int GetNumMethods(void); + Task* GetMethodAtIndex(int index); + + bool DecomposeMethodAtIndex(TaskCallList& decomposition, int index, + const ParameterList& parameters); + + void SetMethods(TaskList* newMethods); +}; + +class CompoundTask +{ +public: + CompoundTask(void) = default; + virtual ~CompoundTask(void) = default; + virtual bool StateMeetsPreconditions(const WorldState& state, + const ParameterList& parameters) const = 0; + virtual bool Decompose(TaskCallList& taskCallList, const WorldState& state, + const ParameterList& parameters) = 0; +}; + +class PrimitiveTask +{ +public: + PrimitiveTask(void) = default; + virtual ~PrimitiveTask(void) = default; + virtual bool StateMeetsPreconditions(const WorldState& state, + const ParameterList& parameters) const = 0; + virtual void ApplyStateChange(WorldState& state, const ParameterList& parameters) = 0; + // Returns whether or not starting the task was successful (NOT whether the task completed) + // Execution should (when completed etc.) modify the world state the same as ApplyStateChange + // would. Call that function for extra safety + virtual bool Execute(WorldState& state, const ParameterList& parameters) = 0; +}; + +enum class TaskType +{ + None = 0, + Goal, + Compound, + Primitive +}; + +// Instead of using inheritance, just use a simple struct which stores all types of tasks but +// only allow only one thing to be filled in for it +struct Task +{ + Task(void) = delete; + Task(GoalTask* goal); + Task(CompoundTask* compound); + Task(PrimitiveTask* primitive); + + TaskType GetType(void) const; + + GoalTask* GetGoal(void); + CompoundTask* GetCompound(void); + PrimitiveTask* GetPrimitive(void); + + friend std::ostream& operator<<(std::ostream& os, const Task& task); + +private: + union + { + GoalTask* Goal; + CompoundTask* Compound; + PrimitiveTask* Primitive; + }; + + TaskType Type; +}; + +std::ostream& operator<<(std::ostream& os, const Task& task); + +void printTaskList(const TaskList& tasks); +void printTaskCallList(const TaskCallList& tasks); +} \ No newline at end of file diff --git a/src/ai/htn/HTNTypes.hpp b/src/ai/htn/HTNTypes.hpp new file mode 100644 index 0000000..9990316 --- /dev/null +++ b/src/ai/htn/HTNTypes.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "../entityComponentSystem/EntityTypes.hpp" + +namespace Htn +{ +// Htn::Parameter is used as a way to send arbitrary arguments between different types of Tasks +// TODO: Add getters/setters so this is safe to use +struct Parameter +{ + enum class ParamType + { + None = 0, + + // Primitives + Int, + Float, + Bool, + + // Game-specific + Entity + }; + + ParamType Type; + + union + { + int IntValue; + float FloatValue; + bool BoolValue; + + Entity EntityValue; + }; +}; + +typedef std::vector ParameterList; +typedef std::vector::iterator ParameterListIterator; +typedef std::vector::const_iterator ParameterListConstIterator; +typedef std::vector::reverse_iterator ParameterListReverseIterator; + +// The arguments passed to most all Task functions +typedef int WorldState; // TODO +} \ No newline at end of file diff --git a/src/experiments/flatbuffers/SavedHello.bin b/src/experiments/flatbuffers/SavedHello.bin new file mode 100644 index 0000000..2ab8b2a Binary files /dev/null and b/src/experiments/flatbuffers/SavedHello.bin differ diff --git a/src/experiments/flatbuffers/testFlatbuffers_write b/src/experiments/flatbuffers/testFlatbuffers_write new file mode 100755 index 0000000..cb6e5e2 Binary files /dev/null and b/src/experiments/flatbuffers/testFlatbuffers_write differ diff --git a/src/objectComponent/Component.cpp b/src/objectComponent/Component.cpp deleted file mode 100644 index 0d9e4ab..0000000 --- a/src/objectComponent/Component.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef COMPONENT_CPP -#define COMPONENT_CPP - -#include - -#include "Component.hpp" -#include "ObjectType.hpp" -#include "Object.hpp" - -Component::Component() -{ - -} - -Component::~Component() -{ - -} - -ComponentType Component::getType() -{ - return type; -} -ObjectType Component::getOwnerType() -{ - return ownerType; -} -ObjectID Component::getOwnerID() -{ - return ownerID; -} - -// This function should NEVER return NULL on a valid initialized Component. -// This function is used by ComponentManagers when moving Components in memory -// Make SURE you set your parentObject pointer in initialize()! -Object* Component::getParentObject() -{ - return parentObject; -} - -// Initialize the component. Components should be able to be reused after initialize is called. -// Components should store their parent object's address for later use -// Return false if initialization failed for any reason, and the object creation will -// be aborted -bool Component::initialize(Object* newParentObject) -{ - return false; -} - -// Once all sibling components in an object have been initialize, postInitialize is executed. -// Use this function to find all sibling components that your component is dependent on, as well -// as any component-component initialization communication that might need to happen. -bool Component::postInitialize() -{ - return true; -} - -// The parent Object has been scheduled to be destroyed. preDestroy() is called on every component -// before components are actually destroyed. This is the "last supper" for components. Use -// this time to do any component-component destruction communication -void Component::preDestroy() -{ - -} - -// The parent Object is being destroyed. Perform any destruction necessary here. It is up to -// you to decide what is destroyed in destroy() and what is cleared in initialize(). -void Component::destroy() -{ - -} - -// Perform all logic for your component here -void Component::update(ComponentUpdateParams& updateParams) -{ - -} - -// Output or render as much helpful information as you can about your component in this -// function. -void Component::debugPrint() -{ - std::cout << "WARNING: Undefined debugPrint for component of type " << (int)type << "\n"; -} - -// Copy all data needed to make an exact replica of the current component to the -// new component. This function will be used when an entire Object or Component is moved. -// Return false if there was an error copying (or it is impossible to copy) -// The new component has already been initialized, but not postInitialized (should it be?) -// You should NOT set the current Component's parentObject pointer to the newComponent's -bool Component::copyComponent(Object* newParentObject, Component* newComponent) -{ - return false; -} -#endif diff --git a/src/objectComponent/Component.hpp b/src/objectComponent/Component.hpp deleted file mode 100644 index ef60111..0000000 --- a/src/objectComponent/Component.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef COMPONENT_HPP -#define COMPONENT_HPP - -#include "ObjectID.hpp" -#include "ObjectType.hpp" - -/* --Component-- - * Components are modular bits of logic and data designed to work with other - * modular components. Objects are collections of components. Component is an abstract - * class. -*/ -enum ComponentType; -struct ComponentUpdateParams; -class Object; -class ComponentManager; -class Component -{ - public: - Component(); - - // Note that the constructor and destructor shouldn't have to be executed - // every time a Component is created. Initialize and Deinitalize should do everything - // necessary for a component to be reused - virtual ~Component(); - - // Getters for types - ComponentType getType(); - ObjectType getOwnerType(); - ObjectID getOwnerID(); - - // This function should NEVER return NULL on a valid initialized Component. - // This function is used by ComponentManagers when moving Components in memory - // Make SURE you set your parentObject pointer in initialize()! - Object* getParentObject(); - - // Initialize the component. Components should be able to be reused after initialize is called. - // Components should store their parent object's address for later use - // Return false if initialization failed for any reason, and the object creation will - // be aborted - virtual bool initialize(Object* newParentObject); - - // Once all sibling components in an object have been initialize, postInitialize is executed. - // Use this function to find all sibling components that your component is dependent on, as well - // as any component-component initialization communication that might need to happen. - virtual bool postInitialize(); - - // The parent Object has been scheduled to be destroyed. preDestroy() is called on every component - // before components are actually destroyed. This is the "last supper" for components. Use - // this time to do any component-component destruction communication - virtual void preDestroy(); - - // The parent Object is being destroyed. Perform any destruction necessary here. It is up to - // you to decide what is destroyed in destroy() and what is cleared in initialize(). - virtual void destroy(); - - // Perform all logic for your component here - virtual void update(ComponentUpdateParams& updateParams); - - // Output or render as much helpful information as you can about your component in this - // function. - virtual void debugPrint(); - - // Copy all data needed to make an exact replica of the current component to the - // new component. This function will be used when an entire Object or Component is moved. - // Return false if there was an error copying (or it is impossible to copy) - // The new component has already been initialized, but not postInitialized (should it be?) - // You should NOT set the current Component's parentObject pointer to the newComponent's - virtual bool copyComponent(Object* newParentObject, Component* newComponent); - - // TODO: Component save and load functions - //virtual bool save(SaveBuffer *saveBuffer); - //virtual bool load(LoadBuffer *loadBuffer); - - // This value can be used by the ComponentManager to find a component - // in a Pool, array, list, etc - int localID; - protected: - - // The parent object of this component. This value should always be - // valid and set as the ComponentManager will use it - Object* parentObject; - - ComponentType type; - private: - ObjectType ownerType; - ObjectID ownerID; -}; - -#endif // COMPONENT_HPP diff --git a/src/objectComponent/ComponentManager.cpp b/src/objectComponent/ComponentManager.cpp deleted file mode 100644 index 7bab115..0000000 --- a/src/objectComponent/ComponentManager.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef COMPONENTMANAGER_CPP -#define COMPONENTMANAGER_CPP - -#include "ComponentManager.hpp" - -ComponentType ComponentManager::getType() -{ - return type; -} - -ComponentManager::ComponentManager() -{ - -} - -ComponentManager::~ComponentManager() -{ - -} - -// Initialize the ComponentManager. The manager should work without -// the constructor having been run before -bool ComponentManager::initialize() -{ - return true; -} - -// Do any destruction necessary for this manager. -void ComponentManager::destroy() -{ - -} - -// Update all of the active Components, passing them the ComponentUpdateParams -void ComponentManager::updateAllComponents(ComponentUpdateParams& componentUpdateParams) -{ - -} - -// Return a pointer to a fresh Component. Do not run initialize() or -// anything on the component - this function's caller should handle that. -// Return NULL if the request cannot be completed (e.g., the pool is full) -Component* ComponentManager::getNewRawComponent() -{ - return nullptr; -} - -// Run destroy() on the Component and remove the component from this manager's memory. -void ComponentManager::destroyComponent(Component* component) -{ - -} - -#endif diff --git a/src/objectComponent/ComponentManager.hpp b/src/objectComponent/ComponentManager.hpp deleted file mode 100644 index fef991e..0000000 --- a/src/objectComponent/ComponentManager.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef COMPONENTMANAGER_HPP -#define COMPONENTMANAGER_HPP - -#include "ComponentTypes.hpp" - -/* --ComponentManager-- - * ComponentManagers handle the storage, creation, and updating of a single - * type of Component. ComponentManager is a polymorphic class so that - * unique behaviors can be determined for all of these functions. - * */ - -class Object; -class Component; -struct ComponentUpdateParams; -class ComponentManager -{ - public: - ComponentManager(); - - virtual ~ComponentManager(); - - ComponentType getType(); - - // Initialize the ComponentManager. The manager should work without - // the constructor having been run before - virtual bool initialize(); - - // Do any destruction necessary for this manager. - virtual void destroy(); - - // Update all of the active Components, passing them the ComponentUpdateParams - virtual void updateAllComponents(ComponentUpdateParams& componentUpdateParams); - - // Return a pointer to a fresh Component. Do not run initialize() or - // anything on the component - this function's caller should handle that. - // Return NULL if the request cannot be completed (e.g., the pool is full) - virtual Component* getNewRawComponent(); - - // Run destroy() on the Component and remove the component from this manager's memory. - virtual void destroyComponent(Component* component); - - private: - ComponentType type; -}; -#endif - diff --git a/src/objectComponent/ComponentTypes.hpp b/src/objectComponent/ComponentTypes.hpp deleted file mode 100644 index dcb4911..0000000 --- a/src/objectComponent/ComponentTypes.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef COMPONENTTYPES_HPP -#define COMPONENTTYPES_HPP - -enum class ComponentType : int -{ - NONE = 0, - TEST -}; - -#endif diff --git a/src/objectComponent/Object.cpp b/src/objectComponent/Object.cpp deleted file mode 100644 index 65da6aa..0000000 --- a/src/objectComponent/Object.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#ifndef OBJECT_CPP -#define OBJECT_CPP - -#include - -#include "Object.hpp" -#include "Component.hpp" - -const int COMPONENTHANDLE_NULL = -1; - - -Object::Object() -{ - initialize(); -} - -Object::~Object() -{ - -} - -void Object::initialize() -{ - shouldDestroy = false; - numActiveComponents = 0; - id = OBJECT_ID_NONE; - type = nullptr; -} - -ObjectID Object::getObjectID() -{ - return id; -} - -// Provides a layer of abstraction around the internal storage of Component pointers -Component* Object::getComponentAtIndex(int i) -{ - if (i >= 0 && i < numActiveComponents) - return components[i]; - return nullptr; -} - -// Searches through components this object is composed of for one of -// the provided type. Returns a handle -// Returns a ComponentHandle which can be used to get a pointer to -// a component of the requested type. -Object::ComponentHandle Object::getComponentHandleForType(ComponentType componentType) -{ - for (int i = 0; i < numActiveComponents; i++) - { - Component* currentComponent = getComponentAtIndex(i); - - if (currentComponent) - { - if (currentComponent->getType() == componentType) - return i; - } - } - - return COMPONENTHANDLE_NULL; -} - -// Returns true if the ComponentHandle is valid, and points to a valid -// Component. -bool Object::isValidComponentHandle(ComponentHandle componentHandle) -{ - if (componentHandle == Object::COMPONENTHANDLE_NULL - || componentHandle >= numActiveComponents) - return false; - return true; -} - -// Returns a pointer to the component indicated by the handle. -// This pointer should NOT be stored on the heap! Components can -// move around in memory, so storing direct pointers is not ok! -Component* Object::getComponent(Object::ComponentHandle componentHandle) -{ - if (isValidComponentHandle(componentHandle)) - return getComponentAtIndex(componentHandle); - return nullptr; -} - -// Returns the value of shouldDestroy. If true, whatever is managing -// the object should notify all components of the impending destruction, -// destroy the components, then destroy the object -bool Object::shouldDestroyObject() -{ - return shouldDestroy; -} - -// Sets the value of shouldDestroy to true. Once set, it cannot be -// undone. The actual destruction should be handled by the object's -// manager after checking shouldDestroyObject() -// This function is meant to be used by the object's components to self-destruct -// Destruction will not happen immediately - it is up to the object's manager -void Object::requestObjectDestruction() -{ - shouldDestroy = true; -} - -void Object::setObjectID(ObjectID newObjectID) -{ - id = newObjectID; -} - -void Object::setObjectType(ObjectType* newObjectType) -{ - type = newObjectType; -} - -void Object::addComponent(Component* newComponent) -{ - if (numActiveComponents < OBJECT_MAX_COMPONENTS) - { - components[numActiveComponents] = newComponent; - numActiveComponents++; - } - else - { - // Tried to add more than OBJECT_MAX_COMPONENTS - assert(0); - } -} - -int Object::getNumActiveComponents() -{ - return numActiveComponents; -} - -// Run postInitialize on all components -bool Object::postInitializeComponents() -{ - // For each active component - for (int i = 0; i < numActiveComponents; i++) - { - Component* currentComponent = getComponentAtIndex(i); - - if (currentComponent) - { - // Run postInitialize on the component - if (!currentComponent->postInitialize()) - return false; - } - } - return true; -} - -// Run preDestroy on all components -void Object::preDestroyComponents() -{ - // For each active component - for (int i = 0; i < numActiveComponents; i++) - { - Component* currentComponent = getComponentAtIndex(i); - - if (currentComponent) - { - currentComponent->preDestroy(); - } - } -} - -// Resets components[] by changing numActiveComponents to 0; this is -// only used if a partially initialized Object's component failed to initialize -void Object::resetComponentsArray() -{ - numActiveComponents = 0; -} -#endif diff --git a/src/objectComponent/Object.hpp b/src/objectComponent/Object.hpp deleted file mode 100644 index 73926dc..0000000 --- a/src/objectComponent/Object.hpp +++ /dev/null @@ -1,113 +0,0 @@ -#ifndef OBJECT_HPP -#define OBJECT_HPP - -#include "ObjectID.hpp" -#include "ObjectType.hpp" -#include "ComponentTypes.hpp" - -/* --Object-- - * Objects hold a collection of Components. Object provides an abstraction - * layer for Components so that components can be moved in memory without - * affecting other Components. - * - * Note that Objects cannot hold more than one Component of the same type. - * If this is required for some reason, you should redesign the system, or - * make the component support it internally -*/ -class Component; -struct ObjectType; -class ObjectComponentManager; -class Object -{ - public: - Object(); - ~Object(); - - // Initialize the object - // Objects should work completely after initialize, even if the constructor isn't called - void initialize(); - - ObjectID getObjectID(); - - // A handle to a Component this Object has - // Components (and all other external users of an Object's components) should - // NEVER store pointers to an Object's components on anything other than - // the stack. ComponentHandles, however, are safe to store. This is so - // that Components can be moved around in memory without breaking references - typedef int ComponentHandle; - - // Searches through components this object is composed of for one of - // the provided type. - // Returns a ComponentHandle which can be used to get a pointer to - // a component of the requested type. - // Note that Objects can only have one Component of the same type - // Check your ComponentHandle's validity with isValidComponentHandle() - ComponentHandle getComponentHandleForType(ComponentType componentType); - - // Returns true if the ComponentHandle is valid, and points to a valid - // Component. - bool isValidComponentHandle(ComponentHandle componentHandle); - - // Returns a pointer to the component indicated by the handle. - // This pointer should NOT be stored on the heap! Components can - // move around in memory, so storing direct pointers is not ok! - // Note that getComponent() checks the validity of the ComponentHandle - // Returns nullptr if the Handle is invalid - Component* getComponent(ComponentHandle componentHandle); - - // Returns the value of shouldDestroy. If true, whatever is managing - // the object should notify all components of the impending destruction, - // destroy the components, then destroy the object - bool shouldDestroyObject(); - - // Sets the value of shouldDestroy to true. Once set, it cannot be - // undone. The actual destruction should be handled by the object's - // manager after checking shouldDestroyObject() - // This function is meant to be used by the object's components to self-destruct - // Destruction will not happen immediately - it is up to the object's manager - void requestObjectDestruction(); - - friend class ObjectComponentManager; - protected: - // Functions for ObjectComponentManager - void setObjectID(ObjectID newObjectID); - void setObjectType(ObjectType* newObjectType); - - // Add a new, initialized component to this object - void addComponent(Component* newComponent); - - // Run postInitialize on all components - bool postInitializeComponents(); - - // Run preDestroy on all components - void preDestroyComponents(); - - int getNumActiveComponents(); - - // Provides a layer of abstraction around the internal storage of Component pointers - Component* getComponentAtIndex(int i); - - // Resets components[] by changing numActiveComponents to 0; this is - // only used if a partially initialized Object's component failed to initialize - void resetComponentsArray(); - - private: - // A list of pointers to Components the Object currently has (up to numActiveComponents) - // OBJECT_MAX_COMPONENTS is defined in ObjectType.hpp - Component* components[OBJECT_MAX_COMPONENTS]; - int numActiveComponents; - - ObjectType* type; - ObjectID id; - - // shouldDestroy indicates whether or not the Object should be - // destroyed. This is meant to be used by Components to self-destruct. - // shouldDestroy should be checked by whatever is managing - // the object after the object's components have been updated - bool shouldDestroy; - - // The NULL value for ComponentHandles - static const int COMPONENTHANDLE_NULL = -1; -}; - -#endif diff --git a/src/objectComponent/ObjectComponentManager.cpp b/src/objectComponent/ObjectComponentManager.cpp deleted file mode 100644 index 6a90f14..0000000 --- a/src/objectComponent/ObjectComponentManager.cpp +++ /dev/null @@ -1,381 +0,0 @@ -#ifndef OBJECTCOMPONENTMANAGER_CPP -#define OBJECTCOMPONENTMANAGER_CPP - -#include -#include - -#include "ObjectComponentManager.hpp" - -#include "ComponentManager.hpp" -#include "Component.hpp" - -ObjectComponentManager::ObjectComponentManager():objectPool(ObjectComponentManager::OBJECT_POOL_SIZE) -{ - -} - -ObjectComponentManager::~ObjectComponentManager() -{ - destroyAllActiveObjects(); -} - -// Destroys all active objects -void ObjectComponentManager::destroyAllActiveObjects() -{ - destroyObjectsWithRequestedDestructionInternal(true); -} - -// Adds a ComponentManager to the ComponentManagerDictionary. If -// there is already a manager for the specified type, it will be overwritten -void ObjectComponentManager::addComponentManager(ComponentType managerComponentType, ComponentManager* newManager) -{ - addComponentManagerToDictionary(managerComponentType, newManager); -} - -// Adds an ObjectType of newObjectTypeID to the ObjectType dictionary. -// If there is already an ObjectType of the specified ObjectTypeID, it will -// be overwritten. This is the function where ObjectTypeIDs are tied to -// a specific ObjectType -void ObjectComponentManager::addObjectType(ObjectTypeID newObjectTypeID, ObjectType newObjectType) -{ - addObjectTypeToDictionary(newObjectTypeID, newObjectType); -} - -// Create a new Object of the specified type. Returns NULL if -// the ObjectType couldn't be found, the object pool is full, or -// Object failed to initialize -// TODO: IMPORTANT! Do extensive testing on this function, making sure things -// are being deleted when they should be if initialization fails for any reason -Object* ObjectComponentManager::createObjectOfTypeID(ObjectTypeID newObjectTypeID) -{ - // Get the new Object's ObjectType - ObjectType* newObjectType = getObjectTypeByID(newObjectTypeID); - - // Couldn't find the requested ObjectType - if (!newObjectType) - return nullptr; - - // Create the Object - Object* newObject = createNewEmptyObject(); - - if (newObject) - { - // Set the Object's ObjectType - newObject->setObjectType(newObjectType); - - // Add components to the new Object - if (createAndAddComponentsForObject(newObject, newObjectType)) - { - // Run postInitialize on the object's components - if (postInitializeObject(newObject)) - return newObject; - } - } - - // Object creation failed; remove the Object - removeEmptyObject(newObject); - return nullptr; -} - -bool ObjectComponentManager::addComponentOfTypeToObject(Object* object, ComponentType componentToAdd) -{ - // Find the ComponentManager for the specified type - ComponentManager* componentManager = getComponentManagerForType(componentToAdd); - - if (!componentManager) - { - // ComponentManager couldn't be found - return false; - } - - // Get a fresh new component from the manager - Component* newComponent = componentManager->getNewRawComponent(); - - if (!newComponent) - { - // Failed to get new component from manager - return false; - } - - // Initialize the component - if (!newComponent->initialize(object)) - { - // Component failed to initialize - return false; - } - - object->addComponent(newComponent); - - return true; -} - -bool ObjectComponentManager::createAndAddComponentsForObject(Object* object, ObjectType* newObjectType) -{ - bool allComponentsSuccessfullyCreated = true; - - if (!object || !newObjectType) - return false; - - // Add components one by one according to the ObjectType spec - for (int i = 0; i < newObjectType->totalUsedComponents; i++) - { - ComponentType currentComponentType = newObjectType->components[i]; - - if (!addComponentOfTypeToObject(object, currentComponentType)) - { - allComponentsSuccessfullyCreated = false; - break; - } - } - - // Make sure the Object has all of its needed components - if (object->getNumActiveComponents() != newObjectType->totalUsedComponents) - { - allComponentsSuccessfullyCreated = false; - } - - if (!allComponentsSuccessfullyCreated) - { - // Something went wrong during Component creation - destroy all other components on the object - // and return failure - destroyAllComponentsOnObject(object); - return false; - } - - return true; -} - -// Tells the ComponentManager of each component on an object to destroy the component -// Make sure to run preDestroy on all of the components before calling this function -// (it is not called within the function because this function is used when Objects -// might not be fully formed (like in createAndAddComponentsForObject) or haven't been -// postInitialized) -void ObjectComponentManager::destroyAllComponentsOnObject(Object* object) -{ - for (int i = 0; i < object->getNumActiveComponents(); i++) - { - Component* currentComponent = object->getComponentAtIndex(i); - - if (!currentComponent) - continue; - - ComponentType currentComponentType = currentComponent->getType(); - - if (currentComponentType != ComponentType::NONE) - { - ComponentManager* currentComponentManager = getComponentManagerForType(currentComponentType); - - if (currentComponentManager) - { - currentComponentManager->destroyComponent(currentComponent); - } - } - } -} - -// Run postInitialize() on all of the Object's components -bool ObjectComponentManager::postInitializeObject(Object* newObject) -{ - if (newObject) - { - return newObject->postInitializeComponents(); - } - - return false; -} - -void ObjectComponentManager::preDestroyObject(Object* object) -{ - if (object) - { - object->preDestroyComponents(); - } -} - -// Update the ComponentManager of the specified type -// There is no updateAll() function because it is expected that there -// will be a specific order the ComponentManagers should be updated. -// Having updateComponentManagerOfType allows that order to be explicitly determined -void ObjectComponentManager::updateComponentManagerOfType(ComponentType componentType, ComponentUpdateParams& componentUpdateParams) -{ - ComponentManager* componentManager = getComponentManagerForType(componentType); - - if (componentManager) - { - componentManager->updateAllComponents(componentUpdateParams); - } - else - { - // Tried to update a component manager which doesn't exist - assert(0); - } -} - -// Destroy all Objects which have shouldDestroy set to true -// This function should be called once per frame/after updating all ComponentManagers -// TODO: This is currently looping through all objects. May need to optimize later -void ObjectComponentManager::destroyObjectsWithRequestedDestruction() -{ - destroyObjectsWithRequestedDestructionInternal(false); -} - -void ObjectComponentManager::destroyObjectsWithRequestedDestructionInternal(bool forceDestroyAllObjects) -{ - std::vector objectsToDestroy; - - // Build a list of all Objects that have impending destruction - // A separate list must be used because any other list could have - // data moving due to the destruction - for (ActiveObjectsDictionary::iterator it = activeObjects.begin(); it != activeObjects.end(); ++it) - { - ObjectPoolHandle currentObjectHandle = it->second; - - if (currentObjectHandle) - { - Object* currentObject = ¤tObjectHandle->data; - - if (currentObject->shouldDestroyObject() || forceDestroyAllObjects) - objectsToDestroy.push_back(currentObjectHandle); - } - } - - // Destroy all Objects in the destroy list - for (std::vector::iterator it = objectsToDestroy.begin(); it != objectsToDestroy.end(); ++it) - { - if (!(*it)) - continue; - - Object* currentObject = &(*it)->data; - - // Have the Object tell its components that they are going to be destroyed - preDestroyObject(currentObject); - - // Destroy the object's components - destroyAllComponentsOnObject(currentObject); - - // Remove the now empty object from the Active Objects dictionary and the Object Pool - removeEmptyObject(currentObject); - } -} - -///////////////////////////////////////////////////////////////// -// Abstraction layer for ObjectComponentManager data structures -///////////////////////////////////////////////////////////////// - -// Creates a new Object, assigns its ID, and adds it to the Active Objects dictionary -// This function does not run initialize() etc -// Make sure to run removeObject() once finished with the object -Object* ObjectComponentManager::createNewEmptyObject() -{ - ObjectPoolHandle newPooledObject = objectPool.getNewData(); - - if (newPooledObject) - { - Object* newObject = &newPooledObject->data; - - // Initialize the object - newObject->initialize(); - - // Set the new object's ID - ObjectID newObjectID = generateUniqueObjectID(); - newObject->setObjectID(newObjectID); - - // Make sure the ID is unique - assert(getObjectByID(newObjectID) == nullptr); - - // Add the Object to the Active Objects Dictionary - activeObjects[newObjectID] = newPooledObject; - - return newObject; - } - - // Pool is full! - std::cout << "WARNING: Object Pool is FULL!\n"; - return nullptr; -} - -Object* ObjectComponentManager::getObjectByID(ObjectID objectID) -{ - ActiveObjectsDictionary::iterator findIt = activeObjects.find(objectID); - - if (findIt != activeObjects.end()) - { - return &findIt->second->data; - } - - return nullptr; -} - -// Removes an object from the ActiveObjects Dictionary and frees it from the object pool. -// This function DOES NOT remove an object's components (hence the "Empty") -// This function should NOT be called if you are iterating over activeObjects, because -// it removes values from activeObjects. -void ObjectComponentManager::removeEmptyObject(Object* objectToRemove) -{ - if (objectToRemove) - { - // Find the Object in the activeObjects dictionary - ActiveObjectsDictionary::iterator findIt = activeObjects.find(objectToRemove->getObjectID()); - - if (findIt != activeObjects.end()) - { - ObjectPoolHandle pooledObject = findIt->second; - - if (pooledObject) - { - // Free the object in the pool - objectPool.removeData(pooledObject); - - // Remove the Object from hte Active Objects dictionary - activeObjects.erase(findIt); - } - } - else - { - // Tried to remove an Object which isn't in this ObjectComponentManager's ActiveObjects - assert(0); - } - } -} - -ComponentManager* ObjectComponentManager::getComponentManagerForType(ComponentType componentType) -{ - ComponentManagerDictionary::iterator findIt = componentManagers.find(componentType); - - if (findIt != componentManagers.end()) - { - return findIt->second; - } - - return nullptr; -} - -void ObjectComponentManager::addComponentManagerToDictionary(ComponentType componentType, ComponentManager* newManager) -{ - componentManagers[componentType] = newManager; -} - -ObjectType* ObjectComponentManager::getObjectTypeByID(ObjectTypeID objectTypeID) -{ - ObjectTypeDictionary::iterator findIt = objectTypes.find(objectTypeID); - - if (findIt != objectTypes.end()) - { - return &findIt->second; - } - - return nullptr; -} - -void ObjectComponentManager::addObjectTypeToDictionary(ObjectTypeID objectTypeID, ObjectType newObjectType) -{ - objectTypes[objectTypeID] = newObjectType; -} - -// TODO: Have a more explicit way to get runtime-unique object ids -static ObjectID currentAvailableObjectID = OBJECT_ID_FIRST; -ObjectID ObjectComponentManager::generateUniqueObjectID() -{ - return currentAvailableObjectID++; -} -#endif diff --git a/src/objectComponent/ObjectComponentManager.hpp b/src/objectComponent/ObjectComponentManager.hpp deleted file mode 100644 index b08a041..0000000 --- a/src/objectComponent/ObjectComponentManager.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef OBJECTCOMPONENTMANAGER_HPP -#define OBJECTCOMPONENTMANAGER_HPP - -#include -#include - -#include "Object.hpp" -#include "ObjectType.hpp" -#include "ComponentTypes.hpp" -#include "ObjectID.hpp" - -/* --ObjectComponentManager-- - * ObjectComponentManager manages ComponentManagers and Objects. It - * performs the necessary interactions between these two systems - * */ -class ComponentManager; -struct ComponentUpdateParams; -class ObjectComponentManager -{ - public: - ObjectComponentManager(); - ~ObjectComponentManager(); - - // Adds a ComponentManager to the ComponentManagerDictionary. If - // there is already a manager for the specified type, it will be overwritten - void addComponentManager(ComponentType managerComponentType, ComponentManager* newManager); - - // Adds an ObjectType of newObjectTypeID to the ObjectType dictionary. - // If there is already an ObjectType of the specified ObjectTypeID, it will - // be overwritten. This is the function where ObjectTypeIDs are tied to - // a specific ObjectType - void addObjectType(ObjectTypeID newObjectTypeID, ObjectType newObjectType); - - // Create a new Object of the specified type. Returns NULL if - // the ObjectType couldn't be found, the object pool is full, or - // Object failed to initalize - Object* createObjectOfTypeID(ObjectTypeID newObjectTypeID); - - // Update the ComponentManager of the specified type - // There is no updateAll() function because it is expected that there - // will be a specific order the ComponentManagers should be updated. - // Having updateComponentManagerOfType allows that order to be explicitly determined - void updateComponentManagerOfType(ComponentType componentType, ComponentUpdateParams& componentUpdateParams); - - // Destroys all active objects - void destroyAllActiveObjects(); - - // Destroy all Objects which have shouldDestroy set to true - // This function should be called once per frame/after updating all ComponentManagers - void destroyObjectsWithRequestedDestruction(); - - private: - // The structure that holds all ComponentManagers - typedef std::map ComponentManagerDictionary; - ComponentManagerDictionary componentManagers; - - // The pooled structure that holds all Objects - typedef Pool ObjectPool; - - static const int OBJECT_POOL_SIZE = 100; - ObjectPool objectPool; - - // A handle to a pooled object - typedef PoolData* ObjectPoolHandle; - - // The dictionary which holds Objects associated with their IDs - typedef std::map ActiveObjectsDictionary; - ActiveObjectsDictionary activeObjects; - - // The structure that holds all ObjectTypes - typedef std::map ObjectTypeDictionary; - ObjectTypeDictionary objectTypes; - - // Abstraction layer for ObjectComponentManager data structures - - // Creates a new Object, assigns its ID, and adds it to the Active Objects dictionary - // This function does not run initialize() etc - // Make sure to run removeObject() once finished with the object - Object* createNewEmptyObject(); - - Object* getObjectByID(ObjectID objectID); - - // Removes an object from the ActiveObjects Dictionary and frees it from the object pool. - // This function DOES NOT remove an object's components (hence the "Empty") - // This function should NOT be called if you are iterating over activeObjects, because - // it removes values from activeObjects. Do not call this function if you are iterating - // over active objects in the object pool either - void removeEmptyObject(Object* objectToRemove); - - ComponentManager* getComponentManagerForType(ComponentType componentType); - - void addComponentManagerToDictionary(ComponentType componentType, ComponentManager* newManager); - - ObjectType* getObjectTypeByID(ObjectTypeID objectTypeID); - - void addObjectTypeToDictionary(ObjectTypeID objectTypeID, ObjectType newObjectType); - - // Generates a globally unique ObjectID (runtime only; these aren't persisted) - ObjectID generateUniqueObjectID(); - - bool addComponentOfTypeToObject(Object* object, ComponentType componentToAdd); - - bool createAndAddComponentsForObject(Object* newObject, ObjectType* newObjectType); - - void destroyObjectsWithRequestedDestructionInternal(bool forceDestroyAllObjects); - - // Tells the ComponentManager of each component on an object to destroy the component - // Make sure to run preDestroy on all of the components before calling this function - // (it is not called within the function because this function is used when Objects - // might not be fully formed (like in createAndAddComponentsForObject) or haven't been - // postInitialized) - void destroyAllComponentsOnObject(Object* newObject); - - // Run postInitialize() on all of the Object's components - bool postInitializeObject(Object* newObject); - - // Run preDestroy() on all of the Object's components - void preDestroyObject(Object* object); -}; -#endif diff --git a/src/objectComponent/ObjectID.cpp b/src/objectComponent/ObjectID.cpp deleted file mode 100644 index d05565d..0000000 --- a/src/objectComponent/ObjectID.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef OBJECTID_CPP -#define OBJECTID_CPP - -#include "ObjectID.hpp" - -// The Object hasn't been assigned an ID -const ObjectID OBJECT_ID_NONE = -1; - -// The last possible ID that can be assigned -// TODO: Set to a better value (this was completely arbitrary) -const ObjectID OBJECT_ID_MAX = 1000000; - -// Start assigning IDs at this value -const ObjectID OBJECT_ID_FIRST = 1; - -#endif diff --git a/src/objectComponent/ObjectID.hpp b/src/objectComponent/ObjectID.hpp deleted file mode 100644 index 2790c29..0000000 --- a/src/objectComponent/ObjectID.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef OBJECTID_HPP -#define OBJECTID_HPP - -typedef int ObjectID; - -// The Object hasn't been assigned an ID -extern const ObjectID OBJECT_ID_NONE; - -// The last possible ID that can be assigned -extern const ObjectID OBJECT_ID_MAX; - -// Start assigning IDs at this value -extern const ObjectID OBJECT_ID_FIRST; - -#endif diff --git a/src/objectComponent/ObjectType.cpp b/src/objectComponent/ObjectType.cpp deleted file mode 100644 index fe413e2..0000000 --- a/src/objectComponent/ObjectType.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef OBJECTTYPE_CPP -#define OBJECTTYPE_CPP - - -#endif diff --git a/src/objectComponent/ObjectType.hpp b/src/objectComponent/ObjectType.hpp deleted file mode 100644 index e6ac3fa..0000000 --- a/src/objectComponent/ObjectType.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef OBJECTTYPE_HPP -#define OBJECTTYPE_HPP - -#include "ComponentTypes.hpp" - -#define OBJECT_MAX_COMPONENTS 10 - -// ObjectTypeIDs are not tied to ObjectTypes automatically. This association -// happens in ObjectComponentManager -enum class ObjectTypeID : int -{ - NONE = 0, - TEST -}; - -struct ObjectType -{ - // An array of ComponentTypes that this ObjectType requires - ComponentType components[OBJECT_MAX_COMPONENTS]; - - // The number of Components this Object will actually use - int totalUsedComponents; -}; - -#endif diff --git a/src/objectComponent/README.txt b/src/objectComponent/README.txt deleted file mode 100644 index d4f976b..0000000 --- a/src/objectComponent/README.txt +++ /dev/null @@ -1,4 +0,0 @@ -ObjectComponent -================ - -ObjectComponent is my first try at getting an Object-Component System (or Entity Component System) set up. Since then, I've redesigned how I want to do it, so this code is *DEPRECATED*. Do not use it. Use src/EntityComponentSystem instead. \ No newline at end of file diff --git a/src/project/galavantSublime/galavant.sublime-project b/src/project/galavantSublime/galavant.sublime-project index bda0a3c..a78fe93 100644 --- a/src/project/galavantSublime/galavant.sublime-project +++ b/src/project/galavantSublime/galavant.sublime-project @@ -5,12 +5,12 @@ "path": "../../../../galavant", "name": "Galavant", "folder_exclude_patterns": ["project"], - "file_include_patterns": ["*.c", "*.cpp", "*.h", "*.hpp", "*.txt", "Jam*"] + "file_include_patterns": ["*.c", "*.cpp", "*.h", "*.hpp", "*.txt", "Jam*", "*.md"] }, { "path": "../../../../galavant-unreal/GalavantUnreal/Source", "name": "Source - Galavant Unreal", - "file_include_patterns": ["*.c", "*.cpp", "*.h", "*.hpp", "*.txt", "*.cs"] + "file_include_patterns": ["*.c", "*.cpp", "*.h", "*.hpp", "*.txt", "*.cs", "*.md"] }, { "path": "../../../gamedata", @@ -42,6 +42,11 @@ "shell_cmd": "make", "working_dir": "../../../src/experiments" }, + { + "name": "Jam", + "shell_cmd": "jam" + }, + // Assume GalavantUnreal is cloned as a submodule or linked (I can't remember if I actually made it a submodule) { "name": "Jam", "shell_cmd": "jam" @@ -50,9 +55,18 @@ "settings": { - //"sublimegdb_workingdir": "${folder:${project_path:your_executable_name}}", - "sublimegdb_workingdir": "../../../../galavant/bin/entityComponentTest", - - "sublimegdb_commandline": "gdb --interpreter=mi ./entityComponentTest" + "sublimegdb_executables": + { + "htnTest": + { + "workingdir": "/home/macoy/Development/code/repositories/galavant/src/unitTesting/bin", + "commandline": "gdb --interpreter=mi ./htnTest" + }, +// "second_executable_name": +// { +// "workingdir": "${folder:${project_path:second_executable_name}}", +// "commandline": "gdb --interpreter=mi ./second_executable" +// } + } } } \ No newline at end of file diff --git a/src/unitTesting/HTN_test.cpp b/src/unitTesting/HTN_test.cpp new file mode 100644 index 0000000..d789b3a --- /dev/null +++ b/src/unitTesting/HTN_test.cpp @@ -0,0 +1,331 @@ +#include + +#define CATCH_CONFIG_MAIN +#include "../../thirdParty/Catch/single_include/catch.hpp" + +#include "../ai/htn/HTNTypes.hpp" +#include "../ai/htn/HTNTasks.hpp" +#include "../ai/htn/HTNPlanner.hpp" + +class AlwaysFailPrimitiveTask : public Htn::PrimitiveTask +{ +public: + AlwaysFailPrimitiveTask(void) = default; + virtual ~AlwaysFailPrimitiveTask(void) = default; + + virtual bool StateMeetsPreconditions(const Htn::WorldState& state, + const Htn::ParameterList& parameters) const + { + std::cout << "\tStateMeetsPreconditions AlwaysFailPrimitiveTask\n"; + return false; + } + + virtual void ApplyStateChange(Htn::WorldState& state, const Htn::ParameterList& parameters) + { + std::cout << "\tApplyStateChange AlwaysFailPrimitiveTask\n"; + } + + virtual bool Execute(Htn::WorldState& state, const Htn::ParameterList& parameters) + { + std::cout << "\texecute AlwaysFailPrimitiveTask: " << parameters[0].IntValue << "\n"; + return false; + } +}; + +class RequiresStatePrimitiveTask : public Htn::PrimitiveTask +{ +public: + RequiresStatePrimitiveTask(void) = default; + virtual ~RequiresStatePrimitiveTask(void) = default; + + virtual bool StateMeetsPreconditions(const Htn::WorldState& state, + const Htn::ParameterList& parameters) const + { + std::cout << "\tStateMeetsPreconditions RequiresStatePrimitiveTask state = " << state + << "\n"; + return state == 1; + } + + virtual void ApplyStateChange(Htn::WorldState& state, const Htn::ParameterList& parameters) + { + std::cout << "\tApplyStateChange RequiresStatePrimitiveTask\n"; + } + + virtual bool Execute(Htn::WorldState& state, const Htn::ParameterList& parameters) + { + std::cout << "\texecute RequiresStatePrimitiveTask: " << parameters[0].IntValue << "\n"; + return true; + } +}; + +class TestPrimitiveTask : public Htn::PrimitiveTask +{ +public: + TestPrimitiveTask(void) = default; + virtual ~TestPrimitiveTask(void) = default; + + virtual bool StateMeetsPreconditions(const Htn::WorldState& state, + const Htn::ParameterList& parameters) const + { + std::cout << "\tStateMeetsPreconditions TestPrimitiveTask\n"; + return true; + } + + virtual void ApplyStateChange(Htn::WorldState& state, const Htn::ParameterList& parameters) + { + std::cout << "\tApplyStateChange TestPrimitiveTask\n"; + state = 1; + } + + virtual bool Execute(Htn::WorldState& state, const Htn::ParameterList& parameters) + { + std::cout << "\texecute TestPrimitiveTask: " << parameters[0].IntValue << "\n"; + return true; + } +}; + +class TestCompoundTaskA : public Htn::CompoundTask +{ +public: + TestCompoundTaskA(void) = default; + + virtual ~TestCompoundTaskA(void) = default; + + virtual bool StateMeetsPreconditions(const Htn::WorldState& state, + const Htn::ParameterList& parameters) const + { + std::cout << "\tStateMeetsPreconditions TestCompoundTaskA\n"; + return true; + } + + virtual bool Decompose(Htn::TaskCallList& taskCallList, const Htn::WorldState& state, + const Htn::ParameterList& parameters) + { + static TestPrimitiveTask testPrimitiveTask; + static Htn::Task primitiveTask(&testPrimitiveTask); + std::cout << "\tDecompose TestCompoundTaskA: " << parameters[0].IntValue << "\n"; + Htn::TaskCall taskCall = {&primitiveTask, parameters}; + taskCallList.push_back(taskCall); + return true; + } +}; + +TEST_CASE("Hierarchical Task Networks Planner") +{ + Htn::Parameter testParam = {Htn::Parameter::ParamType::Int, 123}; + Htn::ParameterList params; + params.push_back(testParam); + + // Base tasks setup + AlwaysFailPrimitiveTask failTask; + RequiresStatePrimitiveTask requiresStateTask; + TestCompoundTaskA testCompoundTaskAA; + + Htn::Task failTaskTask(&failTask); + + Htn::Task requiresStateTaskTask(&requiresStateTask); + Htn::TaskCall requiresStateTaskCall = {&requiresStateTaskTask, params}; + + Htn::Task compoundTask(&testCompoundTaskAA); + Htn::TaskCall taskCall = {&compoundTask, params}; + + // Goal task setup (single) + Htn::GoalTask goalTask; + Htn::TaskList methods; + methods.push_back(&compoundTask); + goalTask.SetMethods(&methods); + Htn::Task goalTaskTask(&goalTask); + Htn::TaskCall goalTaskCall = {&goalTaskTask, params}; + + // Goal task setup (nested) + Htn::GoalTask nestedGoalTask; + Htn::TaskList nestedMethods; + nestedMethods.push_back(&goalTaskTask); + nestedGoalTask.SetMethods(&nestedMethods); + Htn::Task nestedGoalTaskTask(&nestedGoalTask); + Htn::TaskCall nestedGoalTaskCall = {&nestedGoalTaskTask, params}; + + SECTION("Compounds and primitives, but no goals") + { + std::cout << "TEST: Compounds and primitives, but no goals\n\n"; + Htn::Planner testPlan; + testPlan.InitialCallList.push_back(taskCall); + testPlan.InitialCallList.push_back(taskCall); + Htn::WorldState nullState; + testCompoundTaskAA.Decompose(testPlan.InitialCallList, nullState, params); + + Htn::Planner::Status status; + for (int i = 0; i < 10; i++) + { + status = testPlan.PlanStep(); + std::cout << "[" << i << "] Returned Status " << (int)status << "\n"; + if (status == Htn::Planner::Status::PlanComplete) + break; + } + + REQUIRE(status == Htn::Planner::Status::PlanComplete); + REQUIRE(testPlan.FinalCallList.size() == 3); + std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n\n"; + } + + SECTION("One goal (one stack frame)") + { + std::cout << "TEST: One goal (one stack frame)\n\n"; + Htn::Planner testPlan; + testPlan.InitialCallList.push_back(goalTaskCall); + + Htn::Planner::Status status; + for (int i = 0; i < 10; i++) + { + status = testPlan.PlanStep(); + std::cout << "[" << i << "] Returned Status " << (int)status << "\n"; + if (status == Htn::Planner::Status::PlanComplete) + break; + } + + REQUIRE(status == Htn::Planner::Status::PlanComplete); + REQUIRE(testPlan.FinalCallList.size() == 1); + std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n"; + printTaskCallList(testPlan.FinalCallList); + std::cout << "\n\n"; + } + + SECTION("Nested goal (two stack frames)") + { + std::cout << "TEST: Nested goal (two stack frames)\n\n"; + Htn::Planner testPlan; + testPlan.InitialCallList.push_back(nestedGoalTaskCall); + testPlan.InitialCallList.push_back(nestedGoalTaskCall); + + Htn::Planner::Status status; + for (int i = 0; i < 12; i++) + { + status = testPlan.PlanStep(); + std::cout << "[" << i << "] Returned Status " << (int)status << "\n"; + if (status == Htn::Planner::Status::PlanComplete) + break; + } + + REQUIRE(status == Htn::Planner::Status::PlanComplete); + REQUIRE(testPlan.FinalCallList.size() == 2); + std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n"; + printTaskCallList(testPlan.FinalCallList); + std::cout << "\n\n"; + } + + SECTION("Failed decomposition of first goal method (one stack frame)") + { + std::cout << "TEST: Failed decomposition of first goal method (one stack frame)\n\n"; + // Goal task setup (first task fails) + Htn::GoalTask failGoalTask; + Htn::TaskList failMethods = {&failTaskTask, &goalTaskTask}; + // failMethods.push_back(&goalTaskTask); + failGoalTask.SetMethods(&failMethods); + Htn::Task failGoalTaskTask(&failGoalTask); + Htn::TaskCall failGoalTaskCall = {&failGoalTaskTask, params}; + + Htn::Planner testPlan; + testPlan.InitialCallList.push_back(failGoalTaskCall); + + Htn::Planner::Status status; + for (int i = 0; i < 12; i++) + { + status = testPlan.PlanStep(); + std::cout << "[" << i << "] Returned Status " << (int)status << "\n"; + if (status == Htn::Planner::Status::PlanComplete) + break; + } + + REQUIRE(status == Htn::Planner::Status::PlanComplete); + REQUIRE(testPlan.FinalCallList.size() == 1); + std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n"; + printTaskCallList(testPlan.FinalCallList); + std::cout << "\n\n"; + } + + SECTION("Proper state change test (task with dependencies on another task being run before it)") + { + std::cout << "TEST: Proper state change test (task with dependencies on another task being " + "run before it)\n\n"; + Htn::Planner testPlan; + testPlan.InitialCallList.push_back(nestedGoalTaskCall); + testPlan.InitialCallList.push_back(requiresStateTaskCall); + testPlan.State = 0; + + Htn::Planner::Status status; + for (int i = 0; i < 12; i++) + { + status = testPlan.PlanStep(); + std::cout << "[" << i << "] Returned Status " << (int)status << "\n"; + if (status == Htn::Planner::Status::PlanComplete) + break; + } + + REQUIRE(status == Htn::Planner::Status::PlanComplete); + REQUIRE(testPlan.FinalCallList.size() == 2); + std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n"; + printTaskCallList(testPlan.FinalCallList); + std::cout << "\n\n"; + } + + SECTION( + "Proper state change test (task with dependencies on another task being run before it) " + "(This time, fail!)") + { + std::cout << "TEST: Proper state change test (task with dependencies on another task being " + "run before it) (This time, fail!)\n\n"; + Htn::Planner testPlan; + testPlan.InitialCallList.push_back(requiresStateTaskCall); + testPlan.InitialCallList.push_back(nestedGoalTaskCall); + testPlan.State = 0; + + Htn::Planner::Status status; + for (int i = 0; i < 12; i++) + { + status = testPlan.PlanStep(); + std::cout << "[" << i << "] Returned Status " << (int)status << "\n"; + if (status == Htn::Planner::Status::PlanComplete || + status == Htn::Planner::Status::Failed_NoPossiblePlan) + break; + } + + REQUIRE(status == Htn::Planner::Status::Failed_NoPossiblePlan); + REQUIRE(testPlan.FinalCallList.size() == 0); + std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n"; + printTaskCallList(testPlan.FinalCallList); + std::cout << "\n\n"; + } + + SECTION("Test user-specified early break settings") + { + std::cout << "TEST: User-specified early break settings\n\n"; + Htn::Planner testPlan; + testPlan.InitialCallList.push_back(nestedGoalTaskCall); + testPlan.InitialCallList.push_back(requiresStateTaskCall); + testPlan.State = 0; + testPlan.BreakOnStackAction = false; + testPlan.BreakOnCompoundDecomposition = false; + testPlan.BreakOnPrimitiveApply = false; + testPlan.BreakOnFailedDecomposition = false; + + int numIterations = 0; + + Htn::Planner::Status status; + for (int i = 0; i < 12; i++) + { + numIterations++; + status = testPlan.PlanStep(); + std::cout << "[" << i << "] Returned Status " << (int)status << "\n"; + if (status == Htn::Planner::Status::PlanComplete || + status == Htn::Planner::Status::Failed_NoPossiblePlan) + break; + } + + REQUIRE(status == Htn::Planner::Status::PlanComplete); + REQUIRE(testPlan.FinalCallList.size() == 2); + REQUIRE(numIterations == 1); + std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n"; + printTaskCallList(testPlan.FinalCallList); + std::cout << "\n\n"; + } +} \ No newline at end of file diff --git a/src/unitTesting/Jamfile b/src/unitTesting/Jamfile index 8a4693f..00ba1ed 100644 --- a/src/unitTesting/Jamfile +++ b/src/unitTesting/Jamfile @@ -1,13 +1,17 @@ SubDir . src unitTesting ; -Main objectComponentTest : ObjectComponent_test.cpp ../objectComponent/Component.cpp ../objectComponent/ComponentManager.cpp ../objectComponent/Object.cpp ../objectComponent/ObjectComponentManager.cpp ../objectComponent/ObjectID.cpp ../objectComponent/ObjectType.cpp ; - Main entityComponentTest : EntityComponentSystem_test.cpp ; +LinkLibraries entityComponentTest : libGalaEntityComponent ; Main objectPoolTest : ObjectPoolTest.cpp ; -LinkLibraries entityComponentTest : libGalaEntityComponent ; +Main htnTest : HTN_test.cpp ; +LinkLibraries htnTest : libGalaAi ; + +Main positionTest : Position_test.cpp ; +LinkLibraries positionTest : libGalaWorld ; -MakeLocate objectComponentTest entityComponentTest objectPoolTest : bin ; +MakeLocate objectComponentTest entityComponentTest objectPoolTest htnTest positionTest : bin ; -SubInclude . src entityComponentSystem ; \ No newline at end of file +SubInclude . src entityComponentSystem ; +SubInclude . src ai ; \ No newline at end of file diff --git a/src/unitTesting/ObjectComponent_test.cpp b/src/unitTesting/ObjectComponent_test.cpp deleted file mode 100644 index ce91d95..0000000 --- a/src/unitTesting/ObjectComponent_test.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include -#include -#include "../objectComponent/ObjectComponentManager.hpp" -#include "../objectComponent/ObjectType.hpp" -#include "../objectComponent/Component.hpp" -#include "../objectComponent/ComponentManager.hpp" - -struct ComponentUpdateParams -{ - float timeStep; -}; - -class TestComponent : public Component -{ - public: - TestComponent() - { - std::cout << "+++ TestComponent constructed!\n"; - } - virtual ~TestComponent() - { - std::cout << "--- TestComponent destructed\n"; - } - virtual bool initialize(Object* newParentObject) - { - parentObject = newParentObject; - type = ComponentType::TEST; - std::cout << "Initialized new TestComponent!\n"; - return true; - } - virtual bool postInitialize() - { - std::cout << " postInitialized new TestComponent!\n"; - return true; - } - virtual void preDestroy() - { - std::cout << " preDestroy TestComponent!\n"; - } - virtual void destroy() - { - std::cout << "destroy TestComponent!\n"; - } - virtual void update(ComponentUpdateParams& updateParams) - { - std::cout << " updating TestComponent!\n"; - } - virtual void debugPrint() - { - std::cout << " debugPrint TestComponent! parent obj: " << parentObject << "\n"; - } - virtual bool copyComponent(Object* newParentObject, Component* newComponent) - { - std::cout << " Copying TestComponent!\n"; - return true; - } -}; - - -class TestComponentManager : public ComponentManager -{ - private: - Component* activeComponents[100]; - public: - TestComponentManager() - { - std::cout << "+++ TestComponentManager created\n"; - initialize(); - } - virtual ~TestComponentManager() - { - std::cout << "--- TestComponentManager destructor\n"; - destroy(); - } - virtual bool initialize() - { - std::cout << "TestComponentManager initialized\n"; - for (int i = 0; i < 100; i++) - { - activeComponents[i] = nullptr; - } - return true; - } - virtual void destroy() - { - for (int i = 0; i < 100; i++) - { - if (activeComponents[i]) - { - destroyComponent(activeComponents[i]); - } - } - std::cout << "TestComponentManager destroyed\n"; - } - virtual void updateAllComponents(ComponentUpdateParams& componentUpdateParams) - { - std::cout << " Manager: Updating components\n"; - for (int i = 0; i < 100; i++) - { - Component* currentComponent = activeComponents[i]; - - if (currentComponent != nullptr) - { - std::cout << " Manager: Running update on " << currentComponent << "\n"; - currentComponent->update(componentUpdateParams); - } - } - } - virtual Component* getNewRawComponent() - { - std::cout << " Manager: Creating component\n"; - Component* newComponent = new TestComponent; - - for (int i = 0; i < 100; i++) - { - Component* currentComponent = activeComponents[i]; - if (currentComponent == nullptr) - { - newComponent->localID = i; - activeComponents[i] = newComponent; - break; - } - } - - return newComponent; - } - virtual void destroyComponent(Component* component) - { - std::cout << " Manager: Destroying component\n"; - activeComponents[component->localID] = nullptr; - component->destroy(); - delete component; - } -}; - -int main() -{ - std::cout << "Testing ObjectComponent system\n"; - TestComponentManager testComponentManager; - testComponentManager.initialize(); - - ObjectComponentManager objectComponentManager; - objectComponentManager.addComponentManager(ComponentType::TEST, &testComponentManager); - - ObjectType testObject; - testObject.components[0] = ComponentType::TEST; - testObject.totalUsedComponents = 1; - - objectComponentManager.addObjectType(ObjectTypeID::TEST, testObject); - - std::cout << "First object addr: " << objectComponentManager.createObjectOfTypeID(ObjectTypeID::TEST) << "\n"; - - { - ComponentUpdateParams updateParams; - - updateParams.timeStep = 0.016; - - objectComponentManager.updateComponentManagerOfType(ComponentType::TEST, updateParams); - - objectComponentManager.destroyObjectsWithRequestedDestruction(); - } - - std::cout << "Destroying all objects\n"; - - objectComponentManager.destroyAllActiveObjects(); - return 1; -} diff --git a/src/unitTesting/Position_test.cpp b/src/unitTesting/Position_test.cpp new file mode 100644 index 0000000..53353e0 --- /dev/null +++ b/src/unitTesting/Position_test.cpp @@ -0,0 +1,51 @@ +#include + +#define CATCH_CONFIG_MAIN +#include "../../thirdParty/Catch/single_include/catch.hpp" + +#include "../world/Position.hpp" + +TEST_CASE("Position and GlobalPosition") +{ + SECTION("Position Initialization") + { + gv::Position position; + REQUIRE((position.X == 0.f && position.Y == 0.f && position.Z == 0.f)); + + gv::Position position2(1.f, 1.f, 1.f); + REQUIRE((position2.X == 1.f && position2.Y == 1.f && position2.Z == 1.f)); + } + + SECTION("Position Array Accessor") + { + gv::Position position(1.f, 2.f, 3.f); + REQUIRE( + (position.X == position[0] && position.Y == position[1] && position.Z == position[2])); + } + + SECTION("Position Equals") + { + gv::Position a(1.f, 2.f, 3.f); + gv::Position b(1.08f, 2.08f, 3.08f); + REQUIRE(a.Equals(b, 0.1f)); + REQUIRE(!a.Equals(b, 0.01f)); + } + + SECTION("Position Const Operators") + { + gv::Position a(1.f, 2.f, 3.f); + gv::Position b(1.f, 2.f, 3.f); + gv::Position expectedResult(2.f, 4.f, 6.f); + gv::Position result = a + b; + REQUIRE(result.Equals(expectedResult, 0.01f)); + } + + SECTION("Position Modifier Operators") + { + gv::Position a(1.f, 2.f, 3.f); + gv::Position b(1.f, 2.f, 3.f); + gv::Position expectedResult(2.f, 4.f, 6.f); + a += b; + REQUIRE(a.Equals(expectedResult, 0.01f)); + } +} \ No newline at end of file diff --git a/src/world/Jamfile b/src/world/Jamfile new file mode 100644 index 0000000..df9c9b3 --- /dev/null +++ b/src/world/Jamfile @@ -0,0 +1,5 @@ +SubDir . src world ; + +Library libGalaWorld : Position.cpp ; + +MakeLocate libGalaWorld.a : lib ; \ No newline at end of file diff --git a/src/world/Position.cpp b/src/world/Position.cpp new file mode 100644 index 0000000..21ab1f8 --- /dev/null +++ b/src/world/Position.cpp @@ -0,0 +1,101 @@ +#include "Position.hpp" +#include + +namespace gv +{ +Position::Position(float x, float y, float z) : X(x), Y(y), Z(z) +{ +} + +bool Position::Equals(const Position& otherPosition, float tolerance) const +{ + return (fabs(X - otherPosition.X) <= tolerance && fabs(Y - otherPosition.Y) <= tolerance && + fabs(Z - otherPosition.Z) <= tolerance); +} + +float& Position::operator[](int index) +{ + switch (index) + { + case 0: + return X; + case 1: + return Y; + case 2: + return Z; + } + return X; +} + +Position Position::operator+(const Position& otherPosition) const +{ + Position newPosition(*this); + newPosition.X += otherPosition.X; + newPosition.Y += otherPosition.Y; + newPosition.Z += otherPosition.Z; + return newPosition; +} + +Position Position::operator-(const Position& otherPosition) const +{ + Position newPosition(*this); + newPosition.X -= otherPosition.X; + newPosition.Y -= otherPosition.Y; + newPosition.Z -= otherPosition.Z; + return newPosition; +} + +Position Position::operator*(const Position& otherPosition) const +{ + Position newPosition(*this); + newPosition.X *= otherPosition.X; + newPosition.Y *= otherPosition.Y; + newPosition.Z *= otherPosition.Z; + return newPosition; +} + +Position Position::operator/(const Position& otherPosition) const +{ + Position newPosition(*this); + newPosition.X /= otherPosition.X; + newPosition.Y /= otherPosition.Y; + newPosition.Z /= otherPosition.Z; + return newPosition; +} + +Position& Position::operator+=(const Position& otherPosition) +{ + X += otherPosition.X; + Y += otherPosition.Y; + Z += otherPosition.Z; + return *this; +} + +Position& Position::operator-=(const Position& otherPosition) +{ + X -= otherPosition.X; + Y -= otherPosition.Y; + Z -= otherPosition.Z; + return *this; +} + +Position& Position::operator*=(const Position& otherPosition) +{ + X *= otherPosition.X; + Y *= otherPosition.Y; + Z *= otherPosition.Z; + return *this; +} + +Position& Position::operator/=(const Position& otherPosition) +{ + X /= otherPosition.X; + Y /= otherPosition.Y; + Z /= otherPosition.Z; + return *this; +} + +GlobalPosition::GlobalPosition(Position& localPosition) : LocalPosition(localPosition) +{ +} +} \ No newline at end of file diff --git a/src/world/Position.hpp b/src/world/Position.hpp new file mode 100644 index 0000000..7ed2f14 --- /dev/null +++ b/src/world/Position.hpp @@ -0,0 +1,38 @@ +#pragma once + +namespace gv +{ +struct Position +{ + float X = 0.f; + float Y = 0.f; + float Z = 0.f; + + Position() = default; + Position(float x, float y, float z); + + bool Equals(const Position& otherPosition, float tolerance) const; + + float& operator[](int index); + + Position operator+(const Position& otherPosition) const; + Position operator-(const Position& otherPosition) const; + Position operator*(const Position& otherPosition) const; + Position operator/(const Position& otherPosition) const; + + Position& operator+=(const Position& otherPosition); + Position& operator-=(const Position& otherPosition); + Position& operator*=(const Position& otherPosition); + Position& operator/=(const Position& otherPosition); +}; + +struct GlobalPosition +{ + Position LocalPosition; + // Will be added once chunks/world is figured out + // Position ChunkPosition; + + GlobalPosition() = default; + GlobalPosition(Position& localPosition); +}; +}; \ No newline at end of file diff --git a/thirdParty/Catch b/thirdParty/Catch new file mode 160000 index 0000000..e12fc4a --- /dev/null +++ b/thirdParty/Catch @@ -0,0 +1 @@ +Subproject commit e12fc4aca07c3116a2bf8e09b1595b3cfec8fd39 diff --git a/thirdParty/flatbuffers b/thirdParty/flatbuffers index d05d114..1a89682 160000 --- a/thirdParty/flatbuffers +++ b/thirdParty/flatbuffers @@ -1 +1 @@ -Subproject commit d05d1145238d301ed714573183514f95b1b4f89b +Subproject commit 1a8968225130caeddd16e227678e6f8af1926303