Browse Source

AgentComponentManager knows plan status, Added ResourceDictionary

- Renamed HTN task status from Subscribe to WaitForEvent because it's more descriptive
- AgentComponentManager now knows when Plans have finished. This is as of yet untested
- Added AgentGoalDef which describes what an agent should do once its plan completes
- PlanComponentManager now cleans its ReceivedEvents. If there are events from entities which aren't subscribed, they'll no longer pile up
- Added compile-time and runtime CRC32 string hashing utility (copied from StackOverflow because I have no shame)
- Created ResourceDictionary, a dead-simple way to manage things like Defs. It's a key-value store where the key is a CRC'd string and the value is the resource
- Added ResourceDictionaries for NeedDefs and AgentGoalDefs. I'll probably use them for things like HTN tasks
- Cleaned up PlanComponentManager a bit
combatComponentRefactor
Macoy Madson 6 years ago
parent
commit
a53d1c5dd1
  1. 2
      README.md
  2. 19
      TODO.txt
  3. 3
      src/ai/htn/HTNTasks.hpp
  4. 4
      src/entityComponentSystem/EntityTypes.hpp
  5. 1
      src/game/InteractComponentManager.cpp
  6. 1
      src/game/Jamfile
  7. 141
      src/game/agent/AgentComponentManager.cpp
  8. 26
      src/game/agent/AgentComponentManager.hpp
  9. 6
      src/game/agent/Needs.cpp
  10. 5
      src/game/agent/Needs.hpp
  11. 236
      src/game/agent/PlanComponentManager.cpp
  12. 25
      src/game/agent/PlanComponentManager.hpp
  13. 2
      src/game/agent/htnTasks/MovementTasks.cpp
  14. 2
      src/project/galavantSublime/galavant.sublime-project
  15. 10
      src/unitTesting/Jamfile
  16. 31
      src/unitTesting/StringHashing_test.cpp
  17. 3
      src/util/Jamfile
  18. 38
      src/util/ResourceDictionary.hpp
  19. 12
      src/util/StringHashing.cpp
  20. 65
      src/util/StringHashing.hpp

2
README.md

@ -35,6 +35,8 @@ The following libraries are required by Galavant and included in /thirdParty:
- [Catch](https://github.com/philsquared/Catch), created by various contributors/a dude named Travis (Boost Software License)
- [PLog](https://github.com/SergiusTheBest/plog), created by Sergey Podobry (Mozilla Public License v2.0)
Unfortunately, some third party library files needed modification in order to be included. These modifications are kept in src/thirdPartyModifiedFiles. You'll have to diff the modified files with the repository files and copy over my changes.
## 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.

19
TODO.txt

@ -4,22 +4,35 @@ TODO
Soak test editor - after ~1 hour it was looking pretty glitchy
What does agent component manager do with triggers once plan is done? How to know plan is done?
statics don't work well with hot reloading
Need some sort of system which makes it easy to tell static shit to reload?
What does agent component manager do with triggers once plan is done? How to know plan is done?
Pickups sort of working, sometimes they aren't picked up, Actors are being destroyed strangely
Possibility that pickup actor is falling through the floor (invisible other component hitting KillZ?) YES, it's KillZ
HUD Minimap is difficult to use with regards to rotation
HUD Minimap is difficult to use with regards to rotation (needs better view cone)
Minimap noise doesn't match world
Scaling is completely wrong between WorldResource position and noise sample
Actually, this doesn't seem to be the problem
Some sort of resource system
Could be something like ResourceDictionary<key, ResourceType> resources
then things could stuff in things from load or even hard coded (via resources["new thing"] = {})
Put HTN Tasks etc. in resource dictionaries? Who owns them?
----------------------------------------------------------------------------------------------------
DOING
----------------------------------------------------------------------------------------------------
Logging broke as shit
https://answers.unrealengine.com/questions/435366/linux-crash-in-stdstring-destructor.html ?
Test PlanComponent event stuff
----------------------------------------------------------------------------------------------------

3
src/ai/htn/HTNTasks.hpp

@ -4,7 +4,6 @@
#include "../WorldState.hpp"
#include "../../util/CallbackContainer.hpp"
// For std::ostream
#include <iostream>
@ -111,7 +110,7 @@ struct TaskExecuteStatus
Failed = 0,
Succeeded,
Running,
Subscribe,
WaitForEvent,
Reexecute
};

4
src/entityComponentSystem/EntityTypes.hpp

@ -28,7 +28,7 @@ void EntityListAddUniqueEntitiesToSuspect(const EntityList& list, EntityList& su
void EntityListRemoveNonUniqueEntitiesInSuspect(const EntityList& list, EntityList& suspectList);
void EntityListRemoveUniqueEntitiesInSuspect(const EntityList& list, EntityList& suspectList);
// Linear search for entity. I'll eventually add a binary search function if it can be assumed that
// the list is sorted
// Linear search for entity.
// TODO: Add binary search (need to figure out when to sort these lists)
bool EntityListFindEntity(const EntityList& list, Entity entity);
};

1
src/game/InteractComponentManager.cpp

@ -82,7 +82,6 @@ bool InteractComponentManager::PickupDirect(Entity pickupEntity, Entity claimer)
<< needPickupAffects->Def->Name << " by 100 picking up " << pickupEntity;
}
// TODO: Are we allocating for this? :(
EntityList entitiesPickedUp;
entitiesPickedUp.push_back(pickupEntity);
UnsubscribeEntities(entitiesPickedUp);

1
src/game/Jamfile

@ -4,6 +4,7 @@ SubDirC++Flags $(ALLLIBSC++FLAGS) ;
Library libGalaGame : agent/PlanComponentManager.cpp
agent/AgentComponentManager.cpp
agent/Needs.cpp
agent/htnTasks/MovementTasks.cpp
agent/htnTasks/InteractTasks.cpp
InteractComponentManager.cpp

141
src/game/agent/AgentComponentManager.cpp

@ -25,9 +25,15 @@ void AgentComponentManager::Initialize(PlanComponentManager* newPlanComponentMan
void AddGoalIfUniqueType(AgentGoalList& goals, AgentGoal& goalToAdd)
{
if (!goalToAdd.Def)
{
LOGE << "Tried to add AgentGoal with no def!";
return;
}
for (const AgentGoal& goal : goals)
{
if (goalToAdd.Type == goal.Type)
if (goalToAdd.Def->Type == goal.Def->Type)
return;
}
@ -39,6 +45,13 @@ void AgentComponentManager::Update(float deltaSeconds)
EntityList entitiesToUnsubscribe;
EntityList entitiesToDestroy;
PlanComponentManager::PlanComponentList newPlans;
const PlanExecutionEventList& planExecutionEvents = PlanManager->GetExecutionEvents();
if (!PlanManager)
{
LOGE << "Cannot update Agents without PlanManager!";
return;
}
WorldTime += deltaSeconds;
@ -66,54 +79,57 @@ void AgentComponentManager::Update(float deltaSeconds)
// Update Needs
for (Need& need : needs)
{
if (need.Def)
if (!need.Def)
{
bool needUpdated = false;
float updateDelta = WorldTime - need.LastUpdateTime;
while (updateDelta >= need.Def->UpdateRate)
{
need.Level += need.Def->AddPerUpdate;
LOG_ERROR << "Need on entity " << currentEntity << " has no def!";
continue;
}
updateDelta -= need.Def->UpdateRate;
needUpdated = true;
}
bool needUpdated = false;
float updateDelta = WorldTime - need.LastUpdateTime;
while (updateDelta >= need.Def->UpdateRate)
{
need.Level += need.Def->AddPerUpdate;
updateDelta -= need.Def->UpdateRate;
needUpdated = true;
}
if (needUpdated)
{
need.LastUpdateTime = WorldTime;
if (needUpdated)
LOGV_IF(DebugPrint) << "Agent Entity " << currentEntity << " updated need "
<< need.Def->Name << " to level " << need.Level;
for (const NeedLevelTrigger& needLevelTrigger : need.Def->LevelTriggers)
{
need.LastUpdateTime = WorldTime;
bool needTriggerHit =
(needLevelTrigger.GreaterThanLevel && need.Level > needLevelTrigger.Level);
LOGV_IF(DebugPrint) << "Agent Entity " << currentEntity << " updated need "
<< need.Def->Name << " to level " << need.Level;
if (!needTriggerHit)
continue;
for (const NeedLevelTrigger& needLevelTrigger : need.Def->LevelTriggers)
if (needLevelTrigger.NeedsResource && needLevelTrigger.WorldResource)
{
bool needTriggerHit = (needLevelTrigger.GreaterThanLevel &&
need.Level > needLevelTrigger.Level);
if (needTriggerHit)
{
if (needLevelTrigger.NeedsResource && needLevelTrigger.WorldResource)
{
AgentGoal newNeedResourceGoal{AgentGoal::GoalStatus::Initialized,
AgentGoal::GoalType::GetResource,
needLevelTrigger.WorldResource};
AddGoalIfUniqueType(goals, newNeedResourceGoal);
LOGD_IF(DebugPrint) << "Agent Entity " << currentEntity
<< " has hit need trigger for need "
<< need.Def->Name;
}
else if (needLevelTrigger.DieNow)
{
currentComponent->data.IsAlive = false;
LOGD_IF(DebugPrint) << "Agent Entity " << currentEntity
<< " has died from need " << need.Def->Name;
}
}
AgentGoal newNeedResourceGoal{
AgentGoal::GoalStatus::Initialized,
/*NumFailureRetries=*/0,
gv::g_AgentGoalDefDictionary.GetResource(RESKEY("GetResource")),
needLevelTrigger.WorldResource};
AddGoalIfUniqueType(goals, newNeedResourceGoal);
LOGD_IF(DebugPrint) << "Agent Entity " << currentEntity
<< " has hit need trigger for need " << need.Def->Name;
}
else if (needLevelTrigger.DieNow)
{
currentComponent->data.IsAlive = false;
currentComponent->data.ConsciousState = AgentConsciousState::Dead;
LOGD_IF(DebugPrint) << "Agent Entity " << currentEntity
<< " has died from need " << need.Def->Name;
}
}
}
else
LOG_ERROR << "Need on entity " << currentEntity << " has no def!";
}
// Update head goal
@ -128,14 +144,13 @@ void AgentComponentManager::Update(float deltaSeconds)
// TODO: This IsSubscribed call will go away once PlanManager manages multiple
// plans for a single entity. For now, we'll just wait until the entity is no
// longer subscribed before adding our goal
if (PlanManager && !PlanManager->IsSubscribed(currentEntity))
if (!PlanManager->IsSubscribed(currentEntity))
{
if (goal.Type == AgentGoal::GoalType::GetResource)
if (goal.Def->Type == AgentGoalDef::GoalType::GetResource)
{
Htn::Parameter resourceToFind;
resourceToFind.IntValue = goal.WorldResource;
resourceToFind.Type = Htn::Parameter::ParamType::Int;
Htn::ParameterList emptyParams; // TODO: For fuck's sake
Htn::ParameterList parameters = {resourceToFind};
Htn::TaskCall getResourceCall{
Htn::TaskDb::GetTask(Htn::TaskName::GetResource), parameters};
@ -156,23 +171,51 @@ void AgentComponentManager::Update(float deltaSeconds)
goal.Status = AgentGoal::GoalStatus::InProgress;
}
}
else
break;
case AgentGoal::GoalStatus::InProgress:
{
// While goal is in progress, watch planner events for a conclusive status
bool entityConcludedPlan = false;
for (PlanExecutionEvent planEvent : planExecutionEvents)
{
LOG_ERROR << "Agent trying to start goal but no Plan Manager is set!";
goals.erase(headGoalIt);
if (planEvent.entity != currentEntity)
continue;
entityConcludedPlan = true;
if (planEvent.status == PlanExecuteStatus::Failed)
{
if (goal.Def->NumRetriesIfFailed &&
goal.NumFailureRetries < goal.Def->NumRetriesIfFailed)
{
LOGD_IF(DebugPrint)
<< "Entity " << currentEntity << " retrying goal (tried "
<< goal.NumFailureRetries << " times already; "
<< goal.Def->NumRetriesIfFailed << " max tries)";
goal.NumFailureRetries++;
goal.Status = AgentGoal::GoalStatus::Initialized;
entityConcludedPlan = false;
}
}
break;
}
break;
// Fall through if we finished the plan (failed or otherwise)
if (!entityConcludedPlan)
break;
}
case AgentGoal::GoalStatus::Failed:
case AgentGoal::GoalStatus::Succeeded:
goals.erase(headGoalIt);
break;
default:
goals.erase(headGoalIt);
break;
}
}
}
if (PlanManager && !newPlans.empty())
if (!newPlans.empty())
PlanManager->SubscribeEntities(newPlans);
if (!entitiesToUnsubscribe.empty())

26
src/game/agent/AgentComponentManager.hpp

@ -9,6 +9,23 @@
namespace gv
{
struct AgentGoalDef
{
enum class GoalType
{
None = 0,
GetResource,
GoalType_Count
};
GoalType Type;
// TODO: Some sort of waiting period might be a good idea
int NumRetriesIfFailed;
};
extern ResourceDictionary<AgentGoalDef> g_AgentGoalDefDictionary;
struct AgentGoal
{
enum class GoalStatus
@ -21,14 +38,9 @@ struct AgentGoal
};
GoalStatus Status;
enum class GoalType
{
None = 0,
GetResource,
int NumFailureRetries;
GoalType_Count
};
GoalType Type;
AgentGoalDef* Def;
WorldResourceType WorldResource;
};

6
src/game/agent/Needs.cpp

@ -0,0 +1,6 @@
#include "Needs.hpp"
namespace gv
{
ResourceDictionary<NeedDef> g_NeedDefDictionary;
}

5
src/game/agent/Needs.hpp

@ -4,7 +4,8 @@
#include <vector>
#include "../../world/WorldResourceLocator.hpp"
#include "world/WorldResourceLocator.hpp"
#include "util/ResourceDictionary.hpp"
namespace gv
{
@ -46,4 +47,6 @@ struct Need
};
typedef std::vector<Need> NeedList;
extern ResourceDictionary<NeedDef> g_NeedDefDictionary;
}

236
src/game/agent/PlanComponentManager.cpp

@ -25,10 +25,113 @@ void PlanComponentManager::Initialize(WorldStateManager* newWorldStateManager,
taskEventCallbacks->AddCallback(&OnNotify, this);
}
PlanExecuteStatus PlanComponentManager::ExecutePlan(Entity currentEntity,
PlanComponentData& currentComponent,
EntityList& entitiesToUnsubscribe)
{
PlanExecuteStatus planStatus = PlanExecuteStatus::Running;
Htn::TaskCallList::iterator currentStep = currentComponent.Planner.FinalCallList.begin();
if (currentStep != currentComponent.Planner.FinalCallList.end())
{
Htn::ParameterList& parameters = (*currentStep).Parameters;
Htn::Task* task = (*currentStep).TaskToCall;
gv::WorldState& worldState = worldStateManager->GetWorldStateForAgent(currentEntity);
if (task)
{
Htn::TaskExecuteStatus status;
if (currentComponent.WaitingForEvent)
{
for (Htn::TaskEventList::iterator eventIt = ReceivedEvents.begin();
eventIt != ReceivedEvents.end(); ++eventIt)
{
const Htn::TaskEvent& event = (*eventIt);
if (event.entity == currentEntity)
{
LOGD_IF(DebugPrint) << "Entity [" << event.entity
<< "] Task notify returned status "
<< (int)event.Result;
// TODO: This is a hack; make event result mimic execution
// status (we want the same results as that)
status.Status = (event.Result == Htn::TaskEvent::TaskResult::TaskSucceeded ?
Htn::TaskExecuteStatus::Succeeded :
Htn::TaskExecuteStatus::Failed);
ReceivedEvents.erase(eventIt);
currentComponent.WaitingForEvent = false;
break;
}
}
// TODO: Part of the hack above; if we didn't get our event, fake as if
// we ran and it called for a subscribe
if (currentComponent.WaitingForEvent)
status.Status = Htn::TaskExecuteStatus::WaitForEvent;
}
else
{
// We are ignorant of type here because the plan consists only of
// primitives at this point
status = task->GetPrimitive()->Execute(worldState, parameters);
}
switch (status.Status)
{
case Htn::TaskExecuteStatus::Reexecute:
// Don't get rid of the task - execute again
break;
case Htn::TaskExecuteStatus::WaitForEvent:
currentComponent.WaitingForEvent = true;
break;
case Htn::TaskExecuteStatus::Failed:
{
// On failure, unsubscribe. Don't try to replan or anything yet
LOGD_IF(DebugPrint) << "Task failed for entity " << (int)currentEntity;
planStatus = PlanExecuteStatus::Failed;
// Flow through to default remove case
}
// All other statuses result in us getting rid of the task
default:
{
LOGD_IF(DebugPrint) << "Task returned conclusive status " << status.Status;
currentComponent.Planner.FinalCallList.erase(currentStep);
// We have finished all tasks
if (!currentComponent.Planner.FinalCallList.empty())
planStatus = PlanExecuteStatus::Succeeded;
break;
}
}
}
else
currentComponent.Planner.FinalCallList.erase(currentStep);
}
else
{
// We have finished all tasks; remove this entity from the manager
LOGE_IF(DebugPrint) << "Call list empty; something weird happened";
planStatus = PlanExecuteStatus::Failed;
}
if (planStatus == PlanExecuteStatus::Succeeded || planStatus == PlanExecuteStatus::Failed)
{
// TODO: Send event or callback that the plan execution has concluded
entitiesToUnsubscribe.push_back(currentEntity);
}
return planStatus;
}
void PlanComponentManager::Update(float deltaSeconds)
{
EntityList entitiesToUnsubscribe;
// Things had their chance to read these
PlanExecutionEvents.clear();
if (!worldStateManager)
return;
@ -45,100 +148,9 @@ void PlanComponentManager::Update(float deltaSeconds)
Htn::Planner& componentPlanner = currentComponent->data.Planner;
Entity currentEntity = currentComponent->entity;
PlanExecuteStatus planStatus = PlanExecuteStatus::Running;
if (!componentPlanner.IsPlannerRunning())
{
if (componentPlanner.CurrentStatus == Htn::Planner::Status::PlanComplete)
{
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);
if (task)
{
Htn::TaskExecuteStatus status;
if (currentComponent->data.WaitingForEvent)
{
for (Htn::TaskEventList::iterator eventIt = ReceivedEvents.begin();
eventIt != ReceivedEvents.end(); ++eventIt)
{
const Htn::TaskEvent& event = (*eventIt);
if (event.entity == currentEntity)
{
LOGD_IF(DebugPrint) << "Entity [" << event.entity
<< "] Task notify returned status "
<< (int)event.Result;
// TODO: This is a hack; make event result mimic execution
// status (we want the same results as that)
status.Status =
(event.Result == Htn::TaskEvent::TaskResult::TaskSucceeded ?
Htn::TaskExecuteStatus::Succeeded :
Htn::TaskExecuteStatus::Failed);
ReceivedEvents.erase(eventIt);
currentComponent->data.WaitingForEvent = false;
break;
}
}
// TODO: Part of the hack above; if we didn't get our event, fake as if
// we ran and it called for a subscribe
if (currentComponent->data.WaitingForEvent)
status.Status = Htn::TaskExecuteStatus::Subscribe;
}
else
{
// We are ignorant of type here because the plan consists only of
// primitives at this point
status = task->GetPrimitive()->Execute(worldState, parameters);
}
switch (status.Status)
{
case Htn::TaskExecuteStatus::Reexecute:
// Don't get rid of the task - execute again
break;
case Htn::TaskExecuteStatus::Subscribe:
currentComponent->data.WaitingForEvent = true;
break;
case Htn::TaskExecuteStatus::Failed:
// On failure, unsubscribe. Don't try to replan or anything yet
LOGD_IF(DebugPrint) << "Task failed for entity "
<< (int)currentEntity;
entitiesToUnsubscribe.push_back(currentEntity);
// All other statuses result in us getting rid of the task
default:
LOGD_IF(DebugPrint) << "Task returned conclusive status "
<< status.Status;
componentPlanner.FinalCallList.erase(currentStep);
break;
}
}
else
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
LOGD_IF(DebugPrint) << "Call list empty";
entitiesToUnsubscribe.push_back(currentEntity);
}
}
else
{
LOGD_IF(DebugPrint) << "Plan not complete, status "
<< (int)componentPlanner.CurrentStatus;
entitiesToUnsubscribe.push_back(currentEntity);
}
}
else
if (componentPlanner.IsPlannerRunning())
{
Htn::Planner::Status status = componentPlanner.PlanStep();
if (!componentPlanner.IsPlannerRunning())
@ -161,14 +173,43 @@ void PlanComponentManager::Update(float deltaSeconds)
Htn::PrintTaskCallList(componentPlanner.InitialCallList);
// Plan failed, remove entity
// TODO: Hook up events
LOGD_IF(DebugPrint) << "Plan not running/failed";
planStatus = PlanExecuteStatus::Failed;
entitiesToUnsubscribe.push_back(currentEntity);
}
}
}
// Planning complete; resume execution or remove unsuccessful plans
else
{
if (componentPlanner.CurrentStatus == Htn::Planner::Status::PlanComplete)
{
planStatus =
ExecutePlan(currentEntity, currentComponent->data, entitiesToUnsubscribe);
}
else
{
LOGD_IF(DebugPrint) << "Plan not complete, status "
<< (int)componentPlanner.CurrentStatus;
entitiesToUnsubscribe.push_back(currentEntity);
}
}
// Make sure we create an event if there was a conclusive status
if (planStatus > PlanExecuteStatus::Begin_Conclusive)
{
PlanExecutionEvent executionEvent = {currentEntity, planStatus};
PlanExecutionEvents.push_back(executionEvent);
}
}
// Because we just iterated through all things which could've read TaskEvents we've received, we
// want to clear the rest out, because we don't care about them.
// TODO: This is dirty because we have no guarantees that a task won't cause a TaskEvent. They
// should never do that, but they could, then we would miss the event. This whole structure
// needs to be rethought
ReceivedEvents.clear();
if (!entitiesToUnsubscribe.empty())
{
LOGD << "Unsubscribing " << entitiesToUnsubscribe.size() << " entities (we have "
@ -178,7 +219,7 @@ void PlanComponentManager::Update(float deltaSeconds)
}
}
// #Callback: TaskEventCallback
// @Callback: TaskEventCallback
void OnNotify(const Htn::TaskEventList& events, void* userData)
{
if (userData)
@ -226,4 +267,9 @@ void PlanComponentManager::UnsubscribePoolEntitiesInternal(const EntityList& uns
// Perform unsubscription
}
}
const PlanExecutionEventList& PlanComponentManager::GetExecutionEvents() const
{
return PlanExecutionEvents;
}
}

25
src/game/agent/PlanComponentManager.hpp

@ -19,6 +19,24 @@ protected:
bool WaitingForEvent = false;
};
enum class PlanExecuteStatus
{
None = 0,
Running,
Begin_Conclusive,
Succeeded,
Failed,
};
struct PlanExecutionEvent
{
Entity entity;
PlanExecuteStatus status;
};
typedef std::vector<PlanExecutionEvent> PlanExecutionEventList;
/* --PlanComponentManager--
Prepare, manage, and execute plan(s) for Entities.
@ -30,6 +48,11 @@ class PlanComponentManager : public gv::PooledComponentManager<PlanComponentData
private:
WorldStateManager* worldStateManager;
PlanExecutionEventList PlanExecutionEvents;
PlanExecuteStatus ExecutePlan(Entity currentEntity, PlanComponentData& currentComponent,
EntityList& entitiesToUnsubscribe);
protected:
typedef std::vector<gv::PooledComponent<PlanComponentData>*> PlanComponentRefList;
@ -49,6 +72,8 @@ public:
CallbackContainer<Htn::TaskEventCallback>* taskEventCallbacks);
virtual void Update(float deltaSeconds);
const PlanExecutionEventList& GetExecutionEvents() const;
Htn::TaskEventList ReceivedEvents;
};
};

2
src/game/agent/htnTasks/MovementTasks.cpp

@ -83,7 +83,7 @@ Htn::TaskExecuteStatus MoveToTask::Execute(gv::WorldState& state,
LOGD << "Moving Ent[" << state.SourceAgent.SourceEntity << "] to "
<< state.SourceAgent.TargetPosition;
movementManager->PathEntitiesTo(entitiesToMove, positions);
Htn::TaskExecuteStatus status{Htn::TaskExecuteStatus::ExecutionStatus::Subscribe};
Htn::TaskExecuteStatus status{Htn::TaskExecuteStatus::ExecutionStatus::WaitForEvent};
return status;
}

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

@ -4,7 +4,7 @@
{
"path": "../../../../galavant",
"name": "Galavant",
"folder_exclude_patterns": ["project", "thirdParty"],
"folder_exclude_patterns": ["project"],//, "thirdParty"],
"file_include_patterns": ["*.c", "*.cpp", "*.h", "*.hpp", "*.txt", "Jam*", "*.md"]
},
{

10
src/unitTesting/Jamfile

@ -15,7 +15,15 @@ LinkLibraries positionTest : libGalaWorld ;
Main logTest : Log_test.cpp ;
MakeLocate objectComponentTest entityComponentTest objectPoolTest htnTest positionTest logTest : bin ;
Main stringHashTest : StringHashing_test.cpp ;
MakeLocate objectComponentTest
entityComponentTest
objectPoolTest
htnTest
positionTest
logTest
stringHashTest : bin ;
SubInclude . src entityComponentSystem ;
SubInclude . src ai ;

31
src/unitTesting/StringHashing_test.cpp

@ -0,0 +1,31 @@
#pragma once
#include <iostream>
#define CATCH_CONFIG_MAIN
#include "../../thirdParty/Catch/single_include/catch.hpp"
#include "../util/StringHashing.hpp"
TEST_CASE("String Hashing")
{
SECTION("Compile time string hashing matches runtime hashing")
{
const char* dynamicString = "This_Is_A_Test_String_For_Crc_Hashing";
REQUIRE(COMPILE_TIME_CRC32_STR("This_Is_A_Test_String_For_Crc_Hashing") ==
stringCrc32(dynamicString));
}
SECTION("Compile time string hashing does not match other string")
{
REQUIRE(COMPILE_TIME_CRC32_STR("This_Is_A_Test_String_For_Crc_Hashing") !=
COMPILE_TIME_CRC32_STR("This_Is_A_Different_Test_String_For_Crc_Hashing"));
}
SECTION("Empty strings")
{
REQUIRE(!COMPILE_TIME_CRC32_STR(""));
REQUIRE(!stringCrc32(""));
REQUIRE(!stringCrc32(nullptr));
}
}

3
src/util/Jamfile

@ -2,6 +2,7 @@ SubDir . src util ;
SubDirC++Flags $(ALLLIBSC++FLAGS) ;
Library libGalaUtil : Logging.cpp ;
Library libGalaUtil : Logging.cpp
StringHashing.cpp ;
MakeLocate libGalaUtil.a : lib ;

38
src/util/ResourceDictionary.hpp

@ -0,0 +1,38 @@
#pragma once
#include <map>
#include <cassert>
#include "StringHashing.hpp"
#define RESKEY(x) COMPILE_TIME_CRC32_STR(x)
typedef int ResourceKey;
template <class ResourceType>
struct ResourceDictionary
{
std::map<ResourceKey, ResourceType*> Resources;
ResourceType* GetResource(ResourceKey resourceKey)
{
typename std::map<ResourceKey, ResourceType*>::iterator findIt =
Resources.find(resourceKey);
if (findIt == Resources.end())
return nullptr;
return findIt->second;
}
void AddResource(ResourceKey resourceKey, ResourceType* resource)
{
typename std::map<ResourceKey, ResourceType*>::iterator findIt =
Resources.find(resourceKey);
assert(
findIt ==
Resources.end() /*Tried to add a resource with key that is already in the dictionary*/);
Resources[resourceKey] = resource;
}
void ClearResources()
{
Resources.clear();
}
};

12
src/util/StringHashing.cpp

@ -0,0 +1,12 @@
#include "StringHashing.hpp"
// Runtime string hashing
unsigned int stringCrc32(const char* str, unsigned int prev_crc)
{
// Base case
if (!str || !*str)
return prev_crc ^ 0xFFFFFFFF;
// Do not change this function without also changing ConstCrc::crc32()
return stringCrc32(str + 1, (prev_crc >> 8) ^ crc_table[(prev_crc ^ *str) & 0xFF]);
}

65
src/util/StringHashing.hpp

@ -0,0 +1,65 @@
#pragma once
// Compile time and runtime string CRC hashing
// From https://stackoverflow.com/questions/2111667/compile-time-string-hashing
static constexpr unsigned int crc_table[256] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
template <int size, int idx = 0>
struct ConstCrc
{
static constexpr unsigned int crc32(const char* str, unsigned int prev_crc = 0xFFFFFFFF)
{
// Do not change this function without also changing stringCrc32()
return ConstCrc<size, idx + 1>::crc32(
str, (prev_crc >> 8) ^ crc_table[(prev_crc ^ str[idx]) & 0xFF]);
}
};
// This is the stop-recursion function
template <int size>
struct ConstCrc<size, size>
{
static constexpr unsigned int crc32(const char* str, unsigned int prev_crc = 0xFFFFFFFF)
{
return prev_crc ^ 0xFFFFFFFF;
}
};
// This don't take into account the nul char
#define COMPILE_TIME_CRC32_STR(x) (ConstCrc<sizeof(x) - 1>::crc32(x))
// Runtime string hashing
unsigned int stringCrc32(const char* str, unsigned int prev_crc = 0xFFFFFFFF);
Loading…
Cancel
Save