Browse Source
Merging development branch for HTNs into master. I think they're close enough that I can start working with themcombatComponentRefactor

40 changed files with 1527 additions and 1345 deletions
@ -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 |
@ -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 |
@ -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) |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
SubDir . src ai ; |
|||
|
|||
Library libGalaAi : htn/HTNTasks.cpp htn/HTNPlanner.cpp ; |
|||
|
|||
MakeLocate libGalaAi.a : lib ; |
@ -0,0 +1,416 @@ |
|||
#include "HTNPlanner.hpp" |
|||
|
|||
#include "HTNTypes.hpp" |
|||
#include "HTNTasks.hpp" |
|||
|
|||
#include <iostream> |
|||
|
|||
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; |
|||
} |
|||
} |
@ -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<GoalDecomposition> 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); |
|||
}; |
|||
}; |
@ -0,0 +1,114 @@ |
|||
#include "HTNTasks.hpp" |
|||
|
|||
#include <cassert> |
|||
#include <iostream> |
|||
|
|||
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"; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,107 @@ |
|||
#pragma once |
|||
|
|||
#include "HTNTypes.hpp" |
|||
|
|||
#include <iostream> |
|||
|
|||
namespace Htn |
|||
{ |
|||
struct Task; |
|||
typedef std::vector<Task*> TaskList; |
|||
|
|||
struct TaskCall |
|||
{ |
|||
Task* TaskToCall; |
|||
ParameterList Parameters; |
|||
}; |
|||
|
|||
typedef std::vector<TaskCall> 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); |
|||
} |
@ -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<Parameter> ParameterList; |
|||
typedef std::vector<Parameter>::iterator ParameterListIterator; |
|||
typedef std::vector<Parameter>::const_iterator ParameterListConstIterator; |
|||
typedef std::vector<Parameter>::reverse_iterator ParameterListReverseIterator; |
|||
|
|||
// The arguments passed to most all Task functions
|
|||
typedef int WorldState; // TODO
|
|||
} |
Binary file not shown.
Binary file not shown.
@ -1,95 +0,0 @@ |
|||
#ifndef COMPONENT_CPP |
|||
#define COMPONENT_CPP |
|||
|
|||
#include <iostream> |
|||
|
|||
#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 |
@ -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
|
@ -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 |
@ -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 |
|||
|
@ -1,10 +0,0 @@ |
|||
#ifndef COMPONENTTYPES_HPP |
|||
#define COMPONENTTYPES_HPP |
|||
|
|||
enum class ComponentType : int |
|||
{ |
|||
NONE = 0, |
|||
TEST |
|||
}; |
|||
|
|||
#endif |
@ -1,169 +0,0 @@ |
|||
#ifndef OBJECT_CPP |
|||
#define OBJECT_CPP |
|||
|
|||
#include <assert.h> |
|||
|
|||
#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 |
@ -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 |
@ -1,381 +0,0 @@ |
|||
#ifndef OBJECTCOMPONENTMANAGER_CPP |
|||
#define OBJECTCOMPONENTMANAGER_CPP |
|||
|
|||
#include <assert.h> |
|||
#include <iostream> |
|||
|
|||
#include "ObjectComponentManager.hpp" |
|||
|
|||
#include "ComponentManager.hpp" |
|||
#include "Component.hpp" |
|||
|
|||
ObjectComponentManager::ObjectComponentManager():objectPool(ObjectComponentManager::OBJECT_POOL_SIZE) |
|||
{ |
|||
|
|||
} |
|||
|
|||
ObjectComponentManager::~ObjectComponentManager() |
|||
{ |
|||
destroyAllActiveObjects(); |
|||
} |
|||
|
|||