Browse Source

Work all over for HTN vertical slice

- Added EntitySharedData which is for things like Position that almost all components need
- Changed interfaces which didn't hold up to scrutiny
- Fucked with Jamfiles/Jamrules to get Unreal working
- Added Subject Observer, which I will likely refactor heavily, but gives an idea for what it will be like
combatComponentRefactor
Macoy Madson 6 years ago
parent
commit
f345e34457
  1. 1
      Jamfile
  2. 8
      Jamrules
  3. 1
      src/GalavantMain.cpp
  4. 3
      src/Jamfile
  5. 4
      src/ai/Jamfile
  6. 26
      src/ai/WorldState.cpp
  7. 23
      src/ai/WorldState.hpp
  8. 14
      src/ai/htn/HTNPlanner.cpp
  9. 12
      src/ai/htn/HTNPlanner.hpp
  10. 19
      src/ai/htn/HTNTasks.cpp
  11. 16
      src/ai/htn/HTNTasks.hpp
  12. 25
      src/ai/htn/HTNTypes.hpp
  13. 123
      src/entityComponentSystem/EntitySharedData.cpp
  14. 23
      src/entityComponentSystem/EntitySharedData.hpp
  15. 3
      src/entityComponentSystem/Jamfile
  16. 25
      src/entityComponentSystem/PooledComponentManager.hpp
  17. 2
      src/game/Jamfile
  18. 95
      src/game/agent/PlanComponentManager.cpp
  19. 20
      src/game/agent/PlanComponentManager.hpp
  20. 9
      src/project/galavantSublime/galavant.sublime-project
  21. 38
      src/unitTesting/HTN_test.cpp
  22. 8
      src/unitTesting/Position_test.cpp
  23. 47
      src/util/SubjectObserver.hpp
  24. 28
      src/world/Position.cpp
  25. 16
      src/world/Position.hpp

1
Jamfile

@ -1,2 +1,3 @@
SubDir . ;
SubInclude . src ;

8
Jamrules

@ -1,3 +1,6 @@
# Temporary Unreal Command line (TODO: Fix this so there's targets or something)
# jam -j4 -a libGalavant.a libGalaThirdPartyWrapper.a libGalaEntityComponent.a libGalaAi.a libGalaWorld.a libGalaGame.a
##
## Compiler
##
@ -24,8 +27,9 @@ LINK = clang++ ;
# 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
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
HDRS = thirdParty/flatbuffers/include ;

1
src/GalavantMain.cpp

@ -4,5 +4,4 @@
void gv::GalavantMain::Update(float frameTime)
{
std::cout << "Updated Galavant frameTime: " << frameTime << "\n";
}

3
src/Jamfile

@ -1,15 +1,14 @@
SubDir . src ;
Library libGalavant : GalavantMain.cpp ;
LinkLibraries libGalavant : libGalaThirdPartyWrapper ;
MakeLocate libGalavant.a : lib ;
SubInclude . src thirdPartyWrapper ;
SubInclude . src entityComponentSystem ;
SubInclude . src ai ;
SubInclude . src world ;
SubInclude . src game ;
# Experiments and Testing (feel free to remove these if you don't want them built)
SubInclude . src experiments ;

4
src/ai/Jamfile

@ -1,5 +1,7 @@
SubDir . src ai ;
Library libGalaAi : htn/HTNTasks.cpp htn/HTNPlanner.cpp ;
Library libGalaAi : htn/HTNTasks.cpp htn/HTNPlanner.cpp WorldState.cpp ;
LinkLibraries LibGalaAi : LibGalaWorld ;
MakeLocate libGalaAi.a : lib ;

26
src/ai/WorldState.cpp

@ -0,0 +1,26 @@
#include "WorldState.hpp"
#include "../entityComponentSystem/EntitySharedData.hpp"
namespace gv
{
WorldState WorldStateManager::GetWorldStateForAgent(Entity agent)
{
EntityWorldStateMap::iterator findEnt = EntityWorldStates.find(agent);
if (findEnt == EntityWorldStates.end())
{
WorldState worldState;
worldState.SourceAgent.SourceEntity = agent;
EntityWorldStates[agent] = worldState;
findEnt = EntityWorldStates.find(agent);
}
WorldState& worldState = findEnt->second;
// Positions are likely enough to change that we'll just always update it
worldState.SourceAgent.position = (*EntityGetPosition(agent));
return worldState;
}
}

23
src/ai/WorldState.hpp

@ -1,26 +1,43 @@
#pragma once
#include <map>
#include "../world/Position.hpp"
#include "../entityComponentSystem/EntityTypes.hpp"
namespace gv
{
struct AgentState
{
Entity SourceEntity;
Position position;
Position TargetPosition;
};
/* --WorldState--
WorldState represents a mutable, copyable reference to all AI-relevant data in the World.
Mutable: The data can be manipulated freely without repurcussion. Note that changing data in
WorldState is NOT expected to actually change the world - WorldState is like a mirror world
Copyable: The data can be copied without a significant amount of time. This means that in order
to support mutability, things like changelists might need to be implemented for large datasets
instead of actually copying the dataset
Copyable: The data can be copied without a significant performance impact. This means that in
order to support mutability, things like changelists might need to be implemented for large
datasets instead of actually copying the dataset
*/
struct WorldState
{
// Because an agent is almost always going to...well, maybe this shouldn't be here. For now it
// will stay.
AgentState SourceAgent;
// This will be removed eventually
int TestStateChange;
};
class WorldStateManager
{
private:
typedef std::map<Entity, WorldState> EntityWorldStateMap;
EntityWorldStateMap EntityWorldStates;
public:
WorldState GetWorldStateForAgent(Entity agent);
};
};

14
src/ai/htn/HTNPlanner.cpp

@ -8,7 +8,7 @@
namespace Htn
{
bool DecomposeGoalTask(GoalDecompositionStack& decompositionStack, GoalTask* goalTask,
int methodIndex, ParameterList& parameters, WorldState& state)
int methodIndex, ParameterList& parameters, gv::WorldState& state)
{
GoalDecomposition decomposition;
decomposition.DecomposedGoalTask = goalTask;
@ -28,7 +28,7 @@ bool DecomposeGoalTask(GoalDecompositionStack& decompositionStack, GoalTask* goa
}
bool DecomposeCompoundTask(TaskCallList& compoundDecompositions, CompoundTask* compoundTask,
const WorldState& state, const ParameterList& parameters)
const gv::WorldState& state, const ParameterList& parameters)
{
if (!compoundTask->StateMeetsPreconditions(state, parameters))
return false;
@ -36,12 +36,12 @@ bool DecomposeCompoundTask(TaskCallList& compoundDecompositions, CompoundTask* c
return compoundTask->Decompose(compoundDecompositions, state, parameters);
}
bool Planner::IsPlanRunning()
bool Planner::IsPlannerRunning()
{
return IsPlanRunning(CurrentStatus);
return IsPlannerRunning(CurrentStatus);
}
bool Planner::IsPlanRunning(Status status)
bool Planner::IsPlannerRunning(Status status)
{
return (status > Status::Running_EnumBegin && status < Status::Running_EnumEnd);
}
@ -350,7 +350,7 @@ Planner::Status Planner::PlanStep_StackFrame()
{
bool onlyStackFrame = DecompositionStack.size() == 1;
TaskCallList* parentFinalCallList = nullptr;
WorldState* parentWorkingState = nullptr;
gv::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
@ -419,7 +419,7 @@ Planner::Status Planner::PlanStep()
status == Status::Running_FailedMethodDecomposition)))
break;
} while (IsPlanRunning(status));
} while (IsPlannerRunning(status));
CurrentStatus = status;
return status;

12
src/ai/htn/HTNPlanner.hpp

@ -18,11 +18,11 @@ struct GoalDecomposition
TaskCallList CallList;
// The state and parameters at the time this decomposition took place
WorldState InitialState;
gv::WorldState InitialState;
ParameterList Parameters;
// State while the method is being decomposed
WorldState WorkingState;
gv::WorldState WorkingState;
// The result of this stack frame decomposition (only primitive tasks remaining)
TaskCallList FinalCallList;
@ -44,7 +44,7 @@ public:
Planner() = default;
~Planner() = default;
WorldState State;
gv::WorldState State;
TaskCallList InitialCallList;
@ -89,8 +89,8 @@ public:
PlanComplete
};
bool IsPlanRunning();
bool IsPlanRunning(Status status);
bool IsPlannerRunning();
bool IsPlannerRunning(Status status);
Status CurrentStatus;
@ -100,7 +100,7 @@ private:
GoalDecompositionStack DecompositionStack;
// Used for when all goals have been decomposed to manage mutable state
WorldState StacklessState;
gv::WorldState StacklessState;
// Copy of InitialCallList that Planner can fuck with
TaskCallList WorkingCallList;

19
src/ai/htn/HTNTasks.cpp

@ -36,18 +36,33 @@ void GoalTask::SetMethods(TaskList* newMethods)
}
Task::Task(GoalTask* goal)
{
Initialize(goal);
}
Task::Task(CompoundTask* compound)
{
Initialize(compound);
}
Task::Task(PrimitiveTask* primitive)
{
Initialize(primitive);
}
void Task::Initialize(GoalTask* goal)
{
Goal = goal;
Type = TaskType::Goal;
}
Task::Task(CompoundTask* compound)
void Task::Initialize(CompoundTask* compound)
{
Compound = compound;
Type = TaskType::Compound;
}
Task::Task(PrimitiveTask* primitive)
void Task::Initialize(PrimitiveTask* primitive)
{
Primitive = primitive;
Type = TaskType::Primitive;

16
src/ai/htn/HTNTasks.hpp

@ -45,9 +45,9 @@ class CompoundTask
public:
CompoundTask() = default;
virtual ~CompoundTask() = default;
virtual bool StateMeetsPreconditions(const WorldState& state,
virtual bool StateMeetsPreconditions(const gv::WorldState& state,
const ParameterList& parameters) const = 0;
virtual bool Decompose(TaskCallList& taskCallList, const WorldState& state,
virtual bool Decompose(TaskCallList& taskCallList, const gv::WorldState& state,
const ParameterList& parameters) = 0;
};
@ -56,13 +56,13 @@ class PrimitiveTask
public:
PrimitiveTask() = default;
virtual ~PrimitiveTask() = default;
virtual bool StateMeetsPreconditions(const WorldState& state,
virtual bool StateMeetsPreconditions(const gv::WorldState& state,
const ParameterList& parameters) const = 0;
virtual void ApplyStateChange(WorldState& state, const ParameterList& parameters) = 0;
virtual void ApplyStateChange(gv::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;
virtual bool Execute(gv::WorldState& state, const ParameterList& parameters) = 0;
};
enum class TaskType
@ -77,11 +77,15 @@ enum class TaskType
// only allow only one thing to be filled in for it
struct Task
{
Task() = delete;
Task() = default;
Task(GoalTask* goal);
Task(CompoundTask* compound);
Task(PrimitiveTask* primitive);
void Initialize(GoalTask* goal);
void Initialize(CompoundTask* compound);
void Initialize(PrimitiveTask* primitive);
TaskType GetType() const;
GoalTask* GetGoal();

25
src/ai/htn/HTNTypes.hpp

@ -1,6 +1,7 @@
#pragma once
#include "../entityComponentSystem/EntityTypes.hpp"
#include "../world/Position.hpp"
namespace Htn
{
@ -18,7 +19,8 @@ struct Parameter
Bool,
// Game-specific
Entity
Entity,
Position
};
ParamType Type;
@ -30,7 +32,13 @@ struct Parameter
bool BoolValue;
gv::Entity EntityValue;
gv::Position PositionValue;
};
Parameter()
{
new(&PositionValue) gv::Position();
}
};
typedef std::vector<Parameter> ParameterList;
@ -38,6 +46,17 @@ 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: replace with AI WorldState from WorldState.hpp
struct TaskEvent
{
enum class TaskResult
{
None = 0,
TaskSucceeded,
TaskFailed
};
TaskResult Result;
gv::Entity entity;
};
}

123
src/entityComponentSystem/EntitySharedData.cpp

@ -0,0 +1,123 @@
#include "EntitySharedData.hpp"
#include <map>
#include <cassert>
namespace gv
{
typedef std::map<Entity, Position> EntityPositionMap;
typedef std::map<Entity, Position*> EntityPositionRefMap;
struct EntitySharedData
{
// Entity positions with no current owner
EntityPositionMap UnownedEntityPositions;
// Entity positions owned by an external module
EntityPositionRefMap EntityPositionRefs;
};
static EntitySharedData s_Data;
void EntityCreatePositions(const EntityList& entities, PositionRefList& positions)
{
// Should have same number of positions to entities
assert(entities.size() == positions.size());
for (unsigned int i = 0; i < entities.size(); i++)
{
Entity currentEntity = entities[i];
// Ensure we aren't creating positions which have already been created
{
EntityPositionRefMap::iterator findPositionRef =
s_Data.EntityPositionRefs.find(currentEntity);
assert(findPositionRef == s_Data.EntityPositionRefs.end());
}
// Destroy the unowned position for the entity, if it exists
{
EntityPositionMap::iterator findPosition = s_Data.UnownedEntityPositions.find(currentEntity);
if (findPosition != s_Data.UnownedEntityPositions.end())
s_Data.UnownedEntityPositions.erase(findPosition);
}
s_Data.EntityPositionRefs[currentEntity] = positions[i];
}
}
void EntityDestroyPositions(const EntityList& entities)
{
// TODO: This is bad :(
for (EntityPositionRefMap::iterator it = s_Data.EntityPositionRefs.begin();
it != s_Data.EntityPositionRefs.end();)
{
bool foundEntity = false;
for (Entity currentEntity : entities)
{
if (it->first == currentEntity)
{
it = s_Data.EntityPositionRefs.erase(it);
foundEntity = true;
break;
}
}
if (!foundEntity)
++it;
}
}
void EntitySetPositions(const EntityList& entities, const PositionList& positions)
{
// Should have same number of positions to entities
assert(entities.size() == positions.size());
for (unsigned int i = 0; i < entities.size(); i++)
{
Entity currentEntity = entities[i];
Position currentPosition = positions[i];
EntitySetPosition(currentEntity, currentPosition);
}
}
// This sets the value of the position, not the reference (use EntityCreatePosition for that)
void EntitySetPosition(const Entity& entity, const Position& position)
{
EntityPositionRefMap::iterator findRef = s_Data.EntityPositionRefs.find(entity);
if (findRef != s_Data.EntityPositionRefs.end())
(*findRef->second) = position;
else
s_Data.UnownedEntityPositions[entity] = position;
}
void EntityGetPositions(const EntityList& entities, PositionRefList& positionsOut)
{
for (unsigned int i = 0; i < entities.size(); i++)
{
Entity currentEntity = entities[i];
positionsOut[i] = EntityGetPosition(currentEntity);
}
}
// If an entity doesn't have a position, it will be created at 0, 0, 0
Position* EntityGetPosition(const Entity& entity)
{
// Check if we have a reference to the entity's position (another module owns it)
EntityPositionRefMap::iterator findRef = s_Data.EntityPositionRefs.find(entity);
if (findRef != s_Data.EntityPositionRefs.end())
return findRef->second;
// Check if we own the position
EntityPositionMap::iterator findUnowned = s_Data.UnownedEntityPositions.find(entity);
if (findUnowned == s_Data.UnownedEntityPositions.end())
{
// We don't own the position; create it
Position zeroPosition;
s_Data.UnownedEntityPositions[entity] = zeroPosition;
findUnowned = s_Data.UnownedEntityPositions.find(entity);
}
return &findUnowned->second;
}
}

23
src/entityComponentSystem/EntitySharedData.hpp

@ -0,0 +1,23 @@
#pragma once
#include "EntityTypes.hpp"
#include "../world/Position.hpp"
namespace gv
{
// TODO: Make it such that things can own the position and this module is used for lookup externally
// only. If nothing owns the position this module will anonymously own it until someone steps up
// This is so I can follow the principle that "That which changes the data, owns the data". It also
// seems fucking awful to do a map lookup every time anyone wants to read or write a position
// TODO: Make it clear that we expect the given positions to hang around
void EntityCreatePositions(const EntityList& entities, PositionRefList& positions);
void EntityDestroyPositions(const EntityList& entities);
void EntitySetPositions(const EntityList& entities, const PositionList& positions);
void EntitySetPosition(const Entity& entity, const Position& position);
void EntityGetPositions(const EntityList& entities, PositionRefList& positionsOut);
// If an entity doesn't have a position, it will be created at 0, 0, 0
Position* EntityGetPosition(const Entity& entity);
};

3
src/entityComponentSystem/Jamfile

@ -1,5 +1,6 @@
SubDir . src entityComponentSystem ;
Library libGalaEntityComponent : EntityTypes.cpp EntityComponentManager.cpp ComponentManager.cpp ;
Library libGalaEntityComponent : EntityTypes.cpp EntitySharedData.cpp EntityComponentManager.cpp ComponentManager.cpp ;
LinkLibraries libGalaEntityComponent : libGalaWorld ;
MakeLocate libGalaEntityComponent.a : lib ;

25
src/entityComponentSystem/PooledComponentManager.hpp

@ -114,17 +114,24 @@ protected:
// Do whatever your custom manager does for subscribing here.
// The components are already in the pool.
// It is safe to subscribe and unsubscribe components in this function
virtual void SubscribeEntitiesInternal(std::vector<PooledComponent<T>*>& components)
virtual void SubscribeEntitiesInternal(const EntityList& subscribers,
std::vector<PooledComponent<T>*>& components)
{
}
// Do whatever your custom manager does for unsubscribing here.
// The components are still in the subscription list and pool
// It is safe to subscribe and unsubscribe components in this function
virtual void UnsubscribeEntitiesInternal(std::vector<PooledComponent<T>*>& components)
virtual void UnsubscribeEntitiesInternal(const EntityList& unsubscribers,
std::vector<PooledComponent<T>*>& components)
{
}
const EntityList& GetSubscriberList() const
{
return Subscribers;
}
public:
PooledComponentManager(int poolSize) : PooledComponents(poolSize)
{
@ -138,7 +145,8 @@ public:
// If the entity is already subscribed, the input component will be tossed out
void SubscribeEntities(const std::vector<PooledComponent<T>>& components)
{
std::vector<PooledComponent<T>*> newSubscribers(components.size());
std::vector<PooledComponent<T>*> newSubscriberComponents(components.size());
EntityList newSubscribers;
for (typename std::vector<PooledComponent<T>>::const_iterator it = components.begin();
it != components.end(); ++it)
@ -156,14 +164,17 @@ public:
// TODO: handle this elegantly
assert(newPooledComponent);
// TODO: Remove allocation by the caller by letting them pull straight from the pool
newPooledComponent->data = currentPooledComponent;
Subscribers.push_back(currentPooledComponent.entity);
newSubscribers.push_back(currentPooledComponent.entity);
newSubscribers.push_back(&newPooledComponent->data);
newSubscriberComponents.push_back(&newPooledComponent->data);
}
SubscribeEntitiesInternal(newSubscribers);
Subscribers.insert(Subscribers.end(), newSubscribers.begin(), newSubscribers.end());
SubscribeEntitiesInternal(newSubscribers, newSubscriberComponents);
}
virtual void UnsubscribeEntities(const EntityList& entities)
@ -197,7 +208,7 @@ public:
// Note that the child can actually unsubscribe entities in their function. This will
// currently only mean we might try to double-unsubscribe in the code below, which is not
// bad
UnsubscribeEntitiesInternal(unsubscribers);
UnsubscribeEntitiesInternal(entitiesToUnsubscribe, unsubscribers);
// Remove the entities from pool (freeing memory)
for (EntityListConstIterator it = entitiesToUnsubscribe.begin();

2
src/game/Jamfile

@ -2,4 +2,6 @@ SubDir . src game ;
Library libGalaGame : agent/PlanComponentManager.cpp ;
LinkLibraries libGalaGame : libGalaAi libGalaWorld ;
MakeLocate libGalaGame.a : lib ;

95
src/game/agent/PlanComponentManager.cpp

@ -2,6 +2,8 @@
#include "../../entityComponentSystem/PooledComponentManager.hpp"
namespace gv
{
PlanComponentManager::PlanComponentManager() : gv::PooledComponentManager<PlanComponentData>(100)
{
}
@ -10,12 +12,18 @@ PlanComponentManager::~PlanComponentManager()
{
}
void PlanComponentManager::Initialize()
void PlanComponentManager::Initialize(WorldStateManager* newWorldStateManager)
{
worldStateManager = newWorldStateManager;
}
void PlanComponentManager::Update(float deltaSeconds)
{
EntityList entitiesToUnsubscribe;
if (!worldStateManager)
return;
// TODO: Adding true iterator support to pool will drastically help damning this to hell
gv::PooledComponentManager<PlanComponentData>::FragmentedPoolIterator it =
gv::PooledComponentManager<PlanComponentData>::NULL_POOL_ITERATOR;
@ -28,32 +36,74 @@ void PlanComponentManager::Update(float deltaSeconds)
continue;
Htn::Planner& componentPlanner = currentComponent->data.Planner;
Entity currentEntity = currentComponent->entity;
// For now, don't follow plan, just ignore finished/failed plans
if (!componentPlanner.IsPlanRunning())
continue;
Htn::Planner::Status status = componentPlanner.PlanStep();
if (!componentPlanner.IsPlanRunning())
if (!componentPlanner.IsPlannerRunning())
{
if (status == Htn::Planner::Status::PlanComplete)
if (componentPlanner.CurrentStatus == Htn::Planner::Status::PlanComplete)
{
std::cout << "PlanComponentManager: Sucessful plan for Entity "
<< currentComponent->entity << "! Final Call List:\n";
Htn::PrintTaskCallList(componentPlanner.FinalCallList);
}
Htn::TaskCallList::iterator currentStep = componentPlanner.FinalCallList.begin();
if (currentStep != componentPlanner.FinalCallList.end())
{
Htn::ParameterList& parameters = (*currentStep).Parameters;
Htn::Task* task = (*currentStep).TaskToCall;
gv::WorldState worldState =
worldStateManager->GetWorldStateForAgent(currentEntity);
// We are ignorant of type here because the plan consists only of primitives at
// this point
if (task)
task->GetPrimitive()->Execute(worldState, parameters);
if (status < Htn::Planner::Status::Running_EnumBegin)
// Remove the current step because we've executed it.
// TODO: Only remove if the step has finished; handle failed steps
// For now, just edit FinalCallList directly
componentPlanner.FinalCallList.erase(currentStep);
}
else
{
// We have finished all tasks; remove this entity from the manager
// TODO: We'll eventually hook up some event shit
entitiesToUnsubscribe.push_back(currentEntity);
}
}
else
entitiesToUnsubscribe.push_back(currentEntity);
}
else
{
Htn::Planner::Status status = componentPlanner.PlanStep();
if (!componentPlanner.IsPlannerRunning())
{
std::cout << "PlanComponentManager: Failed plan for Entity "
<< currentComponent->entity << "! Initial Call List:\n";
Htn::PrintTaskCallList(componentPlanner.InitialCallList);
if (status == Htn::Planner::Status::PlanComplete)
{
std::cout << "PlanComponentManager: Sucessful plan for Entity "
<< currentComponent->entity << "! Final Call List:\n";
Htn::PrintTaskCallList(componentPlanner.FinalCallList);
// We'll execute the plan next Update()
}
if (status < Htn::Planner::Status::Running_EnumBegin)
{
std::cout << "PlanComponentManager: Failed plan for Entity "
<< currentComponent->entity << " with code " << int(status)
<< "! Initial Call List:\n";
Htn::PrintTaskCallList(componentPlanner.InitialCallList);
// Plan failed, remove entity
// TODO: Hook up events
entitiesToUnsubscribe.push_back(currentEntity);
}
}
}
}
UnsubscribeEntities(entitiesToUnsubscribe);
}
void PlanComponentManager::SubscribeEntitiesInternal(PlanComponentRefList& components)
void PlanComponentManager::SubscribeEntitiesInternal(const EntityList& subscribers,
PlanComponentRefList& components)
{
for (gv::PooledComponent<PlanComponentData>* currentComponent : components)
{
@ -61,14 +111,20 @@ void PlanComponentManager::SubscribeEntitiesInternal(PlanComponentRefList& compo
continue;
Htn::Planner& planner = currentComponent->data.Planner;
Htn::TaskCallList& goalCallList = currentComponent->data.Goals;
Htn::TaskCallList& goalCallList = currentComponent->data.Tasks;
planner.State = worldStateManager->GetWorldStateForAgent(currentComponent->entity);
planner.InitialCallList.insert(planner.InitialCallList.end(), goalCallList.begin(),
goalCallList.end());
// TODO: This is not kosher
planner.CurrentStatus = Htn::Planner::Status::Running_EnumBegin;
planner.DebugPrint = true;
}
}
void PlanComponentManager::UnsubscribeEntitiesInternal(PlanComponentRefList& components)
void PlanComponentManager::UnsubscribeEntitiesInternal(const EntityList& unsubscribers,
PlanComponentRefList& components)
{
for (gv::PooledComponent<PlanComponentData>* currentComponent : components)
{
@ -77,4 +133,5 @@ void PlanComponentManager::UnsubscribeEntitiesInternal(PlanComponentRefList& com
// Perform unsubscription
}
}
}

20
src/game/agent/PlanComponentManager.hpp

@ -1,14 +1,14 @@
#pragma once
#include "../../entityComponentSystem/PooledComponentManager.hpp"
#include "../../ai/htn/HTNPlanner.cpp"
#include "../../ai/htn/HTNPlanner.hpp"
#include "../../ai/WorldState.hpp"
namespace gv
{
struct PlanComponentData
{
gv::WorldState state;
Htn::TaskCallList Goals;
Htn::TaskCallList Tasks;
protected:
friend class PlanComponentManager;
@ -23,17 +23,23 @@ plans.
*/
class PlanComponentManager : public gv::PooledComponentManager<PlanComponentData>
{
private:
WorldStateManager* worldStateManager;
protected:
typedef std::vector<gv::PooledComponent<PlanComponentData>*> PlanComponentRefList;
virtual void SubscribeEntitiesInternal(PlanComponentRefList& components);
virtual void UnsubscribeEntitiesInternal(PlanComponentRefList& components);
virtual void SubscribeEntitiesInternal(const EntityList& subscribers,
PlanComponentRefList& components);
virtual void UnsubscribeEntitiesInternal(const EntityList& unsubscribers,
PlanComponentRefList& components);
public:
typedef std::vector<gv::PooledComponent<PlanComponentData>> PlanComponentList;
PlanComponentManager();
virtual ~PlanComponentManager();
void Initialize();
void Initialize(WorldStateManager* newWorldStateManager);
virtual void Update(float deltaSeconds);
};
};

9
src/project/galavantSublime/galavant.sublime-project

@ -43,13 +43,14 @@
"working_dir": "../../../src/experiments"
},
{
"name": "Jam",
"shell_cmd": "jam"
"name": "Jam Current Directory",
"shell_cmd": "jam -j4 -q"
},
// 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"
"name": "Jam Unreal",
"shell_cmd": "jam -j4 -q -a libGalavant.a libGalaThirdPartyWrapper.a libGalaEntityComponent.a libGalaAi.a libGalaWorld.a libGalaGame.a",
"working_dir": "../../../../galavant"
}
],

38
src/unitTesting/HTN_test.cpp

@ -13,19 +13,19 @@ public:
AlwaysFailPrimitiveTask() = default;
virtual ~AlwaysFailPrimitiveTask() = default;
virtual bool StateMeetsPreconditions(const Htn::WorldState& state,
virtual bool StateMeetsPreconditions(const gv::WorldState& state,
const Htn::ParameterList& parameters) const
{
std::cout << "\tStateMeetsPreconditions AlwaysFailPrimitiveTask\n";
return false;
}
virtual void ApplyStateChange(Htn::WorldState& state, const Htn::ParameterList& parameters)
virtual void ApplyStateChange(gv::WorldState& state, const Htn::ParameterList& parameters)
{
std::cout << "\tApplyStateChange AlwaysFailPrimitiveTask\n";
}
virtual bool Execute(Htn::WorldState& state, const Htn::ParameterList& parameters)
virtual bool Execute(gv::WorldState& state, const Htn::ParameterList& parameters)
{
std::cout << "\texecute AlwaysFailPrimitiveTask: " << parameters[0].IntValue << "\n";
return false;
@ -38,20 +38,20 @@ public:
RequiresStatePrimitiveTask() = default;
virtual ~RequiresStatePrimitiveTask() = default;
virtual bool StateMeetsPreconditions(const Htn::WorldState& state,
virtual bool StateMeetsPreconditions(const gv::WorldState& state,
const Htn::ParameterList& parameters) const
{
std::cout << "\tStateMeetsPreconditions RequiresStatePrimitiveTask state = " << state
<< "\n";
return state == 1;
std::cout << "\tStateMeetsPreconditions RequiresStatePrimitiveTask state.TestStateChange = "
<< state.TestStateChange << "\n";
return state.TestStateChange == 1;
}
virtual void ApplyStateChange(Htn::WorldState& state, const Htn::ParameterList& parameters)
virtual void ApplyStateChange(gv::WorldState& state, const Htn::ParameterList& parameters)
{
std::cout << "\tApplyStateChange RequiresStatePrimitiveTask\n";
}
virtual bool Execute(Htn::WorldState& state, const Htn::ParameterList& parameters)
virtual bool Execute(gv::WorldState& state, const Htn::ParameterList& parameters)
{
std::cout << "\texecute RequiresStatePrimitiveTask: " << parameters[0].IntValue << "\n";
return true;
@ -64,20 +64,20 @@ public:
TestPrimitiveTask() = default;
virtual ~TestPrimitiveTask() = default;
virtual bool StateMeetsPreconditions(const Htn::WorldState& state,
virtual bool StateMeetsPreconditions(const gv::WorldState& state,
const Htn::ParameterList& parameters) const
{
std::cout << "\tStateMeetsPreconditions TestPrimitiveTask\n";
return true;
}
virtual void ApplyStateChange(Htn::WorldState& state, const Htn::ParameterList& parameters)
virtual void ApplyStateChange(gv::WorldState& state, const Htn::ParameterList& parameters)
{
std::cout << "\tApplyStateChange TestPrimitiveTask\n";
state = 1;
state.TestStateChange = 1;
}
virtual bool Execute(Htn::WorldState& state, const Htn::ParameterList& parameters)
virtual bool Execute(gv::WorldState& state, const Htn::ParameterList& parameters)
{
std::cout << "\texecute TestPrimitiveTask: " << parameters[0].IntValue << "\n";
return true;
@ -91,14 +91,14 @@ public:
virtual ~TestCompoundTaskA() = default;
virtual bool StateMeetsPreconditions(const Htn::WorldState& state,
virtual bool StateMeetsPreconditions(const gv::WorldState& state,
const Htn::ParameterList& parameters) const
{
std::cout << "\tStateMeetsPreconditions TestCompoundTaskA\n";
return true;
}
virtual bool Decompose(Htn::TaskCallList& taskCallList, const Htn::WorldState& state,
virtual bool Decompose(Htn::TaskCallList& taskCallList, const gv::WorldState& state,
const Htn::ParameterList& parameters)
{
static TestPrimitiveTask testPrimitiveTask;
@ -151,7 +151,7 @@ TEST_CASE("Hierarchical Task Networks Planner")
Htn::Planner testPlan;
testPlan.InitialCallList.push_back(taskCall);
testPlan.InitialCallList.push_back(taskCall);
Htn::WorldState nullState;
gv::WorldState nullState;
testCompoundTaskAA.Decompose(testPlan.InitialCallList, nullState, params);
Htn::Planner::Status status;
@ -250,7 +250,7 @@ TEST_CASE("Hierarchical Task Networks Planner")
Htn::Planner testPlan;
testPlan.InitialCallList.push_back(nestedGoalTaskCall);
testPlan.InitialCallList.push_back(requiresStateTaskCall);
testPlan.State = 0;
testPlan.State.TestStateChange = 0;
Htn::Planner::Status status;
for (int i = 0; i < 12; i++)
@ -277,7 +277,7 @@ TEST_CASE("Hierarchical Task Networks Planner")
Htn::Planner testPlan;
testPlan.InitialCallList.push_back(requiresStateTaskCall);
testPlan.InitialCallList.push_back(nestedGoalTaskCall);
testPlan.State = 0;
testPlan.State.TestStateChange = 0;
Htn::Planner::Status status;
for (int i = 0; i < 12; i++)
@ -302,7 +302,7 @@ TEST_CASE("Hierarchical Task Networks Planner")
Htn::Planner testPlan;
testPlan.InitialCallList.push_back(nestedGoalTaskCall);
testPlan.InitialCallList.push_back(requiresStateTaskCall);
testPlan.State = 0;
testPlan.State.TestStateChange = 0;
testPlan.BreakOnStackAction = false;
testPlan.BreakOnCompoundDecomposition = false;
testPlan.BreakOnPrimitiveApply = false;

8
src/unitTesting/Position_test.cpp

@ -48,4 +48,12 @@ TEST_CASE("Position and GlobalPosition")
a += b;
REQUIRE(a.Equals(expectedResult, 0.01f));
}
SECTION("Position Explicit Bool")
{
gv::Position a(1.f, 2.f, 3.f);
gv::Position b;
REQUIRE(a);
REQUIRE(!b);
}
}

47
src/util/SubjectObserver.hpp

@ -0,0 +1,47 @@
#pragma once
#include <vector>
#include "entityComponentSystem/EntityTypes.hpp"
namespace gv
{
template <class T>
class Observer
{
public:
Observer() = default;
virtual ~Observer() = default;
virtual void OnNotify(const T& event) = 0;
};
template <class T>
class Subject
{
private:
std::vector<Observer<T>*> Observers;
public:
Subject() = default;
~Subject() = default;
void AddObserver(Observer<T>* observer)
{
if (observer && std::find(Observers.begin(), Observers.end(), observer) == Observers.end())
Observers.push_back(observer);
}
void RemoveObserver(Observer<T>* observer)
{
typename std::vector<Observer<T>*>::iterator foundObserver =
std::find(Observers.begin(), Observers.end(), observer);
if (foundObserver != Observers.end())
Observers.remove(foundObserver);
}
void Notify(const T& event)
{
for (Observer<T>* currentObserver : Observers)
currentObserver->OnNotify(event);
}
};
};

28
src/world/Position.cpp

@ -13,6 +13,29 @@ bool Position::Equals(const Position& otherPosition, float tolerance) const
fabs(Z - otherPosition.Z) <= tolerance);
}
void Position::Reset()
{
X = Y = Z = 0.f;
}
void Position::Set(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
float Position::ManhattanTo(const Position& otherPosition) const
{
return fabs(X - otherPosition.X) + fabs(Y - otherPosition.Y) + fabs(Z - otherPosition.Z);
}
Position::operator bool() const
{
// Only return false if we're exactly zero (don't use tolerances)
return !(X == 0.f && Y == 0.f && Z == 0.f);
}
float& Position::operator[](int index)
{
switch (index)
@ -95,6 +118,11 @@ Position& Position::operator/=(const Position& otherPosition)
return *this;
}
bool Position::operator==(const Position& otherPosition) const
{
return X == otherPosition.X && Y == otherPosition.Y && Z == otherPosition.Z;
}
GlobalPosition::GlobalPosition(Position& localPosition) : LocalPosition(localPosition)
{
}

16
src/world/Position.hpp

@ -1,5 +1,7 @@
#pragma once
#include <vector>
namespace gv
{
struct Position
@ -10,9 +12,18 @@ struct Position
Position() = default;
Position(float x, float y, float z);
~Position() = default;
bool Equals(const Position& otherPosition, float tolerance) const;
void Reset();
void Set(float x, float y, float z);
float ManhattanTo(const Position& otherPosition) const;
// Returns true if the Position is exactly zero
explicit operator bool() const;
float& operator[](int index);
Position operator+(const Position& otherPosition) const;
@ -24,6 +35,8 @@ struct Position
Position& operator-=(const Position& otherPosition);
Position& operator*=(const Position& otherPosition);
Position& operator/=(const Position& otherPosition);
bool operator==(const Position& otherPosition) const;
};
struct GlobalPosition
@ -35,4 +48,7 @@ struct GlobalPosition
GlobalPosition() = default;
GlobalPosition(Position& localPosition);
};
typedef std::vector<Position> PositionList;
typedef std::vector<Position*> PositionRefList;
};
Loading…
Cancel
Save