Browse Source

New logging added to everything but tests; Task enchancements

- I've converted all std::cout calls to LOG calls, except for those in tests, which aren't essential
- Added Logging util for initializing logging and decreasing including plog headers directly
- Goal, Compound, and Primitive Tasks now store and return  Task pointers to themselves. This is to reduce boilerplate and mistakes. I'm now doubting whether avoiding inheritance and static_casts was worth it
combatComponentRefactor
Macoy Madson 6 years ago
parent
commit
0334d64b32
  1. 2
      Jamfile
  2. 3
      src/GalavantMain.cpp
  3. 1
      src/Jamfile
  4. 89
      src/ai/htn/HTNPlanner.cpp
  5. 36
      src/ai/htn/HTNTasks.cpp
  6. 96
      src/ai/htn/HTNTasks.hpp
  7. 26
      src/game/agent/PlanComponentManager.cpp
  8. 28
      src/project/galavantSublime/galavant.sublime-project
  9. 4
      src/unitTesting/HTN_test.cpp
  10. 28
      src/unitTesting/Log_test.cpp
  11. 1
      src/util/FragmentedPool.hpp
  12. 7
      src/util/Jamfile
  13. 30
      src/util/Logging.cpp
  14. 12
      src/util/Logging.hpp
  15. 2
      src/util/SubjectObserver.hpp

2
Jamfile

@ -5,7 +5,7 @@ SubDirC++Flags $(ALLLIBSC++FLAGS) ;
# Lazy pseudotarget for Galavant - make a dummy executable which will make Jam build the libs we're
# actually going to use. There's definitely a better way to do this but I'm too lazy to find it
Main GalavantPseudotarget : src/main.cpp ;
LinkLibraries GalavantPseudotarget : libGalavant libGalaThirdPartyWrapper libGalaEntityComponent libGalaAi libGalaWorld libGalaGame ;
LinkLibraries GalavantPseudotarget : libGalaUtil libGalavant libGalaThirdPartyWrapper libGalaEntityComponent libGalaAi libGalaWorld libGalaGame ;
MakeLocate GalavantPseudotarget : bin ;

3
src/GalavantMain.cpp

@ -1,8 +1,5 @@
#include "GalavantMain.hpp"
#include <plog/Log.h>
void gv::GalavantMain::Update(float frameTime)
{
LOG_VERBOSE << "GalvantMain::Update() Frame Time " << frameTime;
}

1
src/Jamfile

@ -6,6 +6,7 @@ Library libGalavant : GalavantMain.cpp ;
LinkLibraries libGalavant : libGalaThirdPartyWrapper ;
MakeLocate libGalavant.a : lib ;
SubInclude . src util ;
SubInclude . src thirdPartyWrapper ;
SubInclude . src entityComponentSystem ;
SubInclude . src ai ;

89
src/ai/htn/HTNPlanner.cpp

@ -1,10 +1,10 @@
#include "HTNPlanner.hpp"
#include "../../util/Logging.hpp"
#include "HTNTypes.hpp"
#include "HTNTasks.hpp"
#include <iostream>
namespace Htn
{
bool DecomposeGoalTask(GoalDecompositionStack& decompositionStack, GoalTask* goalTask,
@ -66,7 +66,7 @@ Planner::Status Planner::PlanStep_BottomLevel()
if (DebugPrint)
{
std::cout << "\nPlanStep()\nWorkingCallList.size() = " << WorkingCallList.size() << "\n";
LOGD << "PlanStep() WorkingCallList.size() = " << WorkingCallList.size();
PrintTaskCallList(WorkingCallList);
}
@ -80,15 +80,13 @@ Planner::Status Planner::PlanStep_BottomLevel()
continue;
TaskType currentTaskType = currentTask->GetType();
if (DebugPrint)
std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n";
LOGD_IF(DebugPrint) << "TaskType currentTaskType = " << (int)currentTaskType;
switch (currentTaskType)
{
case TaskType::Goal:
{
if (DebugPrint)
std::cout << "Goal\n";
LOGD_IF(DebugPrint) << "Goal";
GoalTask* goalTask = currentTask->GetGoal();
if (!DecomposeGoalTask(DecompositionStack, goalTask, 0, currentTaskCall.Parameters,
@ -101,8 +99,7 @@ Planner::Status Planner::PlanStep_BottomLevel()
break;
case TaskType::Primitive:
{
if (DebugPrint)
std::cout << "Primitive\n";
LOGD_IF(DebugPrint) << "Primitive";
PrimitiveTask* primitiveTask = currentTask->GetPrimitive();
if (!primitiveTask->StateMeetsPreconditions(StacklessState,
currentTaskCall.Parameters))
@ -123,8 +120,7 @@ Planner::Status Planner::PlanStep_BottomLevel()
break;
case TaskType::Compound:
{
if (DebugPrint)
std::cout << "Compound\n";
LOGD_IF(DebugPrint) << "Compound";
CompoundTask* compoundTask = currentTask->GetCompound();
// we need to push our decomposition to our call list, but we're iterating on
@ -143,24 +139,20 @@ Planner::Status Planner::PlanStep_BottomLevel()
if (compoundDecompositions.size())
{
if (DebugPrint)
{
std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size()
<< "\n";
}
LOGD_IF(DebugPrint) << "compoundDecompositions.size() = "
<< compoundDecompositions.size();
WorkingCallList.insert(WorkingCallList.begin(), compoundDecompositions.begin(),
compoundDecompositions.end());
if (DebugPrint)
std::cout << "PlanStep Done\n";
LOGD_IF(DebugPrint) << "PlanStep Done";
// 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";
LOGD_IF(DebugPrint) << "Loop Done";
}
return Status::PlanComplete;
@ -174,26 +166,25 @@ Planner::Status Planner::PlanStep_StackFrame()
if (DebugPrint)
{
std::cout << "\nPlanStep()\ncurrentStackFrame.CallList.size() = "
<< currentStackFrame.CallList.size() << "\n";
LOGD << "PlanStep() currentStackFrame.CallList.size() = "
<< currentStackFrame.CallList.size();
PrintTaskCallList(currentStackFrame.CallList);
std::cout << "Stack Depth: ";
LOGD << "Stack Depth: ";
for (unsigned int i = 0; i < DecompositionStack.size(); i++)
std::cout << "=";
std::cout << "\n";
LOGD << "=";
{
std::cout << "----Fullstack working lists\n";
std::cout << "[0]\n";
LOGD << "----Fullstack working lists";
LOGD << "[0]";
PrintTaskCallList(WorkingCallList);
int i = 1;
for (GoalDecomposition& stackFrame : DecompositionStack)
{
std::cout << "[" << i++ << "]\n";
LOGD << "[" << i++ << "]";
PrintTaskCallList(stackFrame.CallList);
}
std::cout << "----\n";
LOGD << "----";
}
}
@ -210,15 +201,13 @@ Planner::Status Planner::PlanStep_StackFrame()
continue;
TaskType currentTaskType = currentTask->GetType();
if (DebugPrint)
std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n";
LOGD_IF(DebugPrint) << "TaskType currentTaskType = " << (int)currentTaskType;
switch (currentTaskType)
{
case TaskType::Goal:
{
if (DebugPrint)
std::cout << "Goal\n";
LOGD_IF(DebugPrint) << "Goal";
GoalTask* goalTask = currentTask->GetGoal();
// TODO erase ahead of time because fuck
@ -237,8 +226,8 @@ Planner::Status Planner::PlanStep_StackFrame()
// 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 " << &currentStackFrame << " to "
<< &(*(DecompositionStack.end() - 1)) << "\n";
/*LOGD << "Ref updating from " << &currentStackFrame << " to "
<< &(*(DecompositionStack.end() - 1));
currentStackFrameIter = DecompositionStack.end() - 1;
currentStackFrame = *currentStackFrameIter;*/
@ -247,8 +236,7 @@ Planner::Status Planner::PlanStep_StackFrame()
break;
case TaskType::Primitive:
{
if (DebugPrint)
std::cout << "Primitive\n";
LOGD_IF(DebugPrint) << "Primitive";
PrimitiveTask* primitiveTask = currentTask->GetPrimitive();
if (!primitiveTask->StateMeetsPreconditions(currentStackFrame.WorkingState,
currentTaskCall.Parameters))
@ -272,8 +260,7 @@ Planner::Status Planner::PlanStep_StackFrame()
break;
case TaskType::Compound:
{
if (DebugPrint)
std::cout << "Compound\n";
LOGD_IF(DebugPrint) << "Compound";
CompoundTask* compoundTask = currentTask->GetCompound();
if (!DecomposeCompoundTask(compoundDecompositions, compoundTask,
@ -297,8 +284,7 @@ Planner::Status Planner::PlanStep_StackFrame()
if (methodFailed)
{
if (DebugPrint)
std::cout << "Method failed decomposition\n";
LOGD_IF(DebugPrint) << "Method failed decomposition";
// Clear stack frame
currentStackFrame.CallList.clear();
currentStackFrame.FinalCallList.clear();
@ -323,26 +309,22 @@ Planner::Status Planner::PlanStep_StackFrame()
{
if (DebugPrint)
{
std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size()
<< "\n";
std::cout << "currentStackFrame.CallList.size() = "
<< currentStackFrame.CallList.size() << "\n";
std::cout << "Decomposition:\n";
LOGD << "compoundDecompositions.size() = " << compoundDecompositions.size();
LOGD << "currentStackFrame.CallList.size() = " << currentStackFrame.CallList.size();
LOGD << "Decomposition:";
PrintTaskCallList(compoundDecompositions);
}
currentStackFrame.CallList.insert(currentStackFrame.CallList.begin(),
compoundDecompositions.begin(),
compoundDecompositions.end());
if (DebugPrint)
std::cout << "PlanStep Done\n";
LOGD_IF(DebugPrint) << "PlanStep Done";
// 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";
LOGD_IF(DebugPrint) << "Loop Done";
}
// Finished processing this stack frame
@ -369,9 +351,9 @@ Planner::Status Planner::PlanStep_StackFrame()
if (DebugPrint)
{
std::cout << "Collapsing stack frame. Adding List:\n";
LOGD << "Collapsing stack frame. Adding List:";
PrintTaskCallList(currentStackFrame.FinalCallList);
std::cout << "To parent:\n";
LOGD << "To parent:";
PrintTaskCallList(*parentFinalCallList);
}
@ -383,8 +365,7 @@ Planner::Status Planner::PlanStep_StackFrame()
DecompositionStack.pop_back();
if (DebugPrint)
std::cout << "Frame Done\n";
LOGD_IF(DebugPrint) << "Frame Done";
return Status::Running_SuccessfulDecompositionStackPop;
}

36
src/ai/htn/HTNTasks.cpp

@ -1,7 +1,8 @@
#include "HTNTasks.hpp"
#include <cassert>
#include <iostream>
#include "../../util/Logging.hpp"
namespace Htn
{
@ -35,6 +36,27 @@ void GoalTask::SetMethods(TaskList* newMethods)
Methods = newMethods;
}
Task* GoalTask::GetTask()
{
if (TaskContainer.GetType() == TaskType::None)
TaskContainer.Initialize(this);
return &TaskContainer;
}
Task* PrimitiveTask::GetTask()
{
if (TaskContainer.GetType() == TaskType::None)
TaskContainer.Initialize(this);
return &TaskContainer;
}
Task* CompoundTask::GetTask()
{
if (TaskContainer.GetType() == TaskType::None)
TaskContainer.Initialize(this);
return &TaskContainer;
}
Task::Task(GoalTask* goal)
{
Initialize(goal);
@ -111,19 +133,15 @@ std::ostream& operator<<(std::ostream& os, const Task& task)
void PrintTaskList(const TaskList& tasks)
{
std::cout << "TaskList size = " << tasks.size() << " addr " << &tasks << ":\n";
LOGD << "TaskList size = " << tasks.size() << " addr " << &tasks << ":";
for (unsigned int i = 0; i < tasks.size(); i++)
{
std::cout << "\t[" << i << "] " << *tasks[i] << "\n";
}
LOGD << " [" << i << "] " << *tasks[i];
}
void PrintTaskCallList(const TaskCallList& tasks)
{
std::cout << "TaskCallList size = " << tasks.size() << " addr " << &tasks << ":\n";
LOGD << "TaskCallList size = " << tasks.size() << " addr " << &tasks << ":";
for (unsigned int i = 0; i < tasks.size(); i++)
{
std::cout << "\t[" << i << "] " << *tasks[i].TaskToCall << "\n";
}
LOGD << " [" << i << "] " << *tasks[i].TaskToCall;
}
}

96
src/ai/htn/HTNTasks.hpp

@ -3,11 +3,54 @@
#include "HTNTypes.hpp"
#include "../WorldState.hpp"
// For std::ostream
#include <iostream>
namespace Htn
{
struct Task;
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
class GoalTask;
class CompoundTask;
class PrimitiveTask;
struct Task
{
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();
CompoundTask* GetCompound();
PrimitiveTask* GetPrimitive();
friend std::ostream& operator<<(std::ostream& os, const Task& task);
private:
union
{
GoalTask* Goal;
CompoundTask* Compound;
PrimitiveTask* Primitive;
};
TaskType Type = TaskType::None;
};
typedef std::vector<Task*> TaskList;
struct TaskCall
@ -27,6 +70,7 @@ class GoalTask
{
private:
TaskList* Methods;
Task TaskContainer;
public:
GoalTask() = default;
@ -38,10 +82,15 @@ public:
const ParameterList& parameters);
void SetMethods(TaskList* newMethods);
Task* GetTask();
};
class CompoundTask
{
private:
Task TaskContainer;
public:
CompoundTask() = default;
virtual ~CompoundTask() = default;
@ -49,10 +98,15 @@ public:
const ParameterList& parameters) const = 0;
virtual bool Decompose(TaskCallList& taskCallList, const gv::WorldState& state,
const ParameterList& parameters) = 0;
Task* GetTask();
};
class PrimitiveTask
{
private:
Task TaskContainer;
public:
PrimitiveTask() = default;
virtual ~PrimitiveTask() = default;
@ -63,46 +117,8 @@ public:
// Execution should (when completed etc.) modify the world state the same as ApplyStateChange
// would. Call that function for extra safety
virtual bool Execute(gv::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() = 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();
CompoundTask* GetCompound();
PrimitiveTask* GetPrimitive();
friend std::ostream& operator<<(std::ostream& os, const Task& task);
private:
union
{
GoalTask* Goal;
CompoundTask* Compound;
PrimitiveTask* Primitive;
};
TaskType Type;
Task* GetTask();
};
std::ostream& operator<<(std::ostream& os, const Task& task);

26
src/game/agent/PlanComponentManager.cpp

@ -1,5 +1,7 @@
#include "PlanComponentManager.hpp"
#include "../../util/Logging.hpp"
#include "../../entityComponentSystem/PooledComponentManager.hpp"
namespace gv
@ -65,14 +67,14 @@ void PlanComponentManager::Update(float deltaSeconds)
{
// We have finished all tasks; remove this entity from the manager
// TODO: We'll eventually hook up some event shit
std::cout << "PlanComponentManager: Call list empty\n";
LOGD << "PlanComponentManager: Call list empty";
entitiesToUnsubscribe.push_back(currentEntity);
}
}
else
{
std::cout << "PlanComponentManager: Plan not complete, status "
<< (int)componentPlanner.CurrentStatus << "\n";
LOGD << "PlanComponentManager: Plan not complete, status "
<< (int)componentPlanner.CurrentStatus;
entitiesToUnsubscribe.push_back(currentEntity);
}
}
@ -83,30 +85,30 @@ void PlanComponentManager::Update(float deltaSeconds)
{
if (status == Htn::Planner::Status::PlanComplete)
{
std::cout << "PlanComponentManager: Sucessful plan for Entity "
<< currentComponent->entity << "! Final Call List:\n";
LOGD << "PlanComponentManager: Sucessful plan for Entity "
<< currentComponent->entity << "! Final Call List:";
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";
LOGD << "PlanComponentManager: Failed plan for Entity "
<< currentComponent->entity << " with code " << int(status)
<< "! Initial Call List:";
Htn::PrintTaskCallList(componentPlanner.InitialCallList);
// Plan failed, remove entity
// TODO: Hook up events
std::cout << "PlanComponentManager: Plan not running/failed\n";
LOGD << "PlanComponentManager: Plan not running/failed";
entitiesToUnsubscribe.push_back(currentEntity);
}
}
}
}
std::cout << "PlanComponentManager: Unsubscribed " << entitiesToUnsubscribe.size()
<< " entities\n";
LOGD_IF(entitiesToUnsubscribe.size()) << "PlanComponentManager: Unsubscribed "
<< entitiesToUnsubscribe.size() << " entities";
UnsubscribeEntities(entitiesToUnsubscribe);
}
@ -130,7 +132,7 @@ void PlanComponentManager::SubscribeEntitiesInternal(const EntityList& subscribe
planner.DebugPrint = true;
}
std::cout << "PlanComponentManager: Subscribed " << components.size() << " entities\n";
LOGD << "PlanComponentManager: Subscribed " << components.size() << " entities";
}
void PlanComponentManager::UnsubscribeEntitiesInternal(const EntityList& unsubscribers,

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

@ -42,14 +42,24 @@
"shell_cmd": "make",
"working_dir": "../../../src/experiments"
},
{
"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 Clean All",
"shell_cmd": "jam clean",
"working_dir": "../../../../galavant"
},
{
"name": "Jam Build (not Unreal)",
"shell_cmd": "jam -j4 -q",
"working_dir": "../../../../galavant"
},
{
"name": "Jam Unreal",
"shell_cmd": "jam -j4 -q -a libGalavant.a libGalaThirdPartyWrapper.a libGalaEntityComponent.a libGalaAi.a libGalaWorld.a libGalaGame.a",
"shell_cmd": "jam -j4 -q -sUNREAL=true GalavantPseudotarget",
"working_dir": "../../../../galavant"
}
],
@ -58,17 +68,17 @@
{
"sublimegdb_executables":
{
"htnTest":
{
"workingdir": "/home/macoy/Development/code/repositories/galavant/src/unitTesting/bin",
"commandline": "gdb --interpreter=mi ./htnTest"
},
"UnrealEditor":
{
// sudo gdb /home/macoy/Downloads/UnrealEngine/Engine/Binaries/Linux/UE4Editor $(ps -A | grep UE4Editor | awk '{print $1;}')
"workingdir": "/home/macoy/Downloads/UnrealEngine/Engine/Binaries/Linux",
"commandline": "gdb --interpreter=mi ./UE4Editor"
"commandline": "gdb --interpreter=mi ./UE4Editor /home/macoy/Development/code/repositories/galavant-unreal/GalavantUnreal/GalavantUnreal.uproject"
},
"htnTest":
{
"workingdir": "../../../bin",
"commandline": "gdb --interpreter=mi ./htnTest"
},
// "second_executable_name":

4
src/unitTesting/HTN_test.cpp

@ -3,6 +3,8 @@
#define CATCH_CONFIG_MAIN
#include "../../thirdParty/Catch/single_include/catch.hpp"
#include "../util/Logging.hpp"
#include "../ai/htn/HTNTypes.hpp"
#include "../ai/htn/HTNTasks.hpp"
#include "../ai/htn/HTNPlanner.hpp"
@ -112,6 +114,8 @@ public:
TEST_CASE("Hierarchical Task Networks Planner")
{
gv::InitializeConsoleOnlyLogging();
Htn::Parameter testParam;
testParam.Type = Htn::Parameter::ParamType::Int;
testParam.IntValue = 123;

28
src/unitTesting/Log_test.cpp

@ -9,20 +9,20 @@
#include <plog/Formatters/FuncMessageFormatter.h>
#include <list>
#include "../util/Logging.hpp"
// Copied from thirdParty/plog/samples/CustomAppender/Main.cpp
namespace plog
{
template <class Formatter> // Typically a formatter is passed as a template parameter.
class MyAppender : public IAppender // All appenders MUST inherit IAppender interface.
template <class Formatter>
class MyAppender : public IAppender
{
public:
virtual void write(
const Record& record) // This is a method from IAppender that MUST be implemented.
virtual void write(const Record& record)
{
util::nstring str =
Formatter::format(record); // Use the formatter to get a string from a record.
util::nstring str = Formatter::format(record);
m_messageList.push_back(str); // Store a log message in a list.
m_messageList.push_back(str);
}
std::list<util::nstring>& getMessageList()
@ -59,4 +59,18 @@ TEST_CASE("Log")
REQUIRE(!myAppender.getMessageList().empty());
}
SECTION("Log Console Appender")
{
static plog::ColorConsoleAppender<plog::FuncMessageFormatter> s_ConsoleLogAppender;
plog::init(plog::debug, &s_ConsoleLogAppender);
for (int i = 0; i < 10; i++)
LOGD << "[" << i << "]";
LOGD << "You should see 1 - 9 above";
// I don't know how to test this
REQUIRE(true);
}
}

1
src/util/FragmentedPool.hpp

@ -2,7 +2,6 @@
#define POOL_HPP
#include <stdlib.h>
#include <vector>
#include <iostream>
// This is some old code copied from the Horizon iteration. It's due for a rewrite.
// TODO: Add standard iterator syntax (See ObjectPool.hpp)

7
src/util/Jamfile

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

30
src/util/Logging.cpp

@ -0,0 +1,30 @@
#include "Logging.hpp"
#include <plog/Formatters/FuncMessageFormatter.h>
#include <plog/Appenders/ColorConsoleAppender.h>
namespace gv
{
static bool gs_LoggingInitialized = false;
void InitializeConsoleOnlyLogging()
{
static plog::ColorConsoleAppender<plog::FuncMessageFormatter> s_ConsoleLogAppender;
if (!gs_LoggingInitialized)
{
plog::init(plog::debug, &s_ConsoleLogAppender);
gs_LoggingInitialized = true;
}
}
// Feel free to pass in NULL to get the default log file
void InitializeFileOnlyLogging(const char* filename)
{
if (!gs_LoggingInitialized)
{
plog::init(plog::debug, filename ? filename : "LOCAL_Galavant.log");
gs_LoggingInitialized = true;
}
}
}

12
src/util/Logging.hpp

@ -0,0 +1,12 @@
#pragma once
// Including Plog here for convenience and abstraction; that way users can just include Logging.hpp
#include <plog/Log.h>
namespace gv
{
void InitializeConsoleOnlyLogging();
// Feel free to pass in NULL to get the default log file
void InitializeFileOnlyLogging(const char* filename);
}

2
src/util/SubjectObserver.hpp

@ -38,6 +38,8 @@ public:
if (foundObserver != Observers.end())
Observers.remove(foundObserver);
}
// TODO: This could be improved by sending all events in one Notify...
void Notify(const T& event)
{
for (Observer<T>* currentObserver : Observers)

Loading…
Cancel
Save