Browse Source
- I tested writing Flatbuffers binaries out to JSON, which is cool. - I made some changes to the ECS interface, added EntityList helpers, and implemented a simple pooled component manager. - Note that ObjectPool is unfinished, and may be discarded (I'm having troubles coming up with a way to do handles).combatComponentRefactor

15 changed files with 586 additions and 36 deletions
@ -1,58 +1,202 @@ |
|||
#ifndef POOLEDCOMPONENTMANAGER_H__ |
|||
#define POOLEDCOMPONENTMANAGER_H__ |
|||
|
|||
// TODO: Handle full pool better (will be done when FragmentedPool is replaced)
|
|||
#include <cassert> |
|||
#include <vector> |
|||
|
|||
#include <iostream> |
|||
|
|||
// TODO: Replace FragmentedPool with a better pool
|
|||
#include "../util/FragmentedPool.hpp" |
|||
#include "EntityTypes.hpp" |
|||
#include "ComponentManager.hpp" |
|||
|
|||
/* --PooledComponentManager--
|
|||
PooledComponentManager is a general purpose ComponentManager that assumes you're managing your |
|||
PooledComponentManager is a general purpose PooledComponentManager that assumes you're managing your |
|||
components in a standard way. |
|||
|
|||
You should only use PooledComponentManager if it is a good fit for your manager (i.e. you don't need |
|||
to do anything special in Subscribe/Unsubscribe etc.) - in other words, use the right tool for the |
|||
job. Writing a ComponentManager from scratch is not difficult. Read through the assumptions. If your |
|||
job. Writing a PooledComponentManager from scratch is not difficult. Read through the assumptions. |
|||
If your |
|||
use case doesn't match all of those assumptions, you should write a specialized manager instead. |
|||
|
|||
Assumptions |
|||
------------ |
|||
All components have identical data. |
|||
|
|||
Whatever subscribes entities knows all of the data needed to create a Component. |
|||
Whatever subscribes entities knows all of the data needed to create a PooledComponent. |
|||
|
|||
All components are treated the exact same way. For example, if you have a field |
|||
GiveMeSpecialTreatment on your component, it would be better to remove that field and instead have |
|||
all components desiring special treatment in a SpecialTreatment list. This makes it so you don't |
|||
have to have complicated branching deep in your Component update loop to give special treatment. If |
|||
have to have complicated branching deep in your PooledComponent update loop to give special |
|||
treatment. If |
|||
this is the case, you probably shouldn't be using PooledComponentManager. |
|||
|
|||
Whatever your data is can be copied via the assignment operator '=' |
|||
*/ |
|||
|
|||
template <class T> |
|||
struct Component |
|||
struct PooledComponent |
|||
{ |
|||
Entity entity; |
|||
T data; |
|||
|
|||
void operator=(const PooledComponent<T>& component) |
|||
{ |
|||
entity = component.entity; |
|||
data = component.data; |
|||
} |
|||
}; |
|||
|
|||
template <class T> |
|||
class PooledComponentManager : public ComponentManager |
|||
{ |
|||
private: |
|||
// This list is used only to quickly check whether an entity is subscribed; data should actually
|
|||
// be stored in PooledComponents
|
|||
EntityList Subscribers; |
|||
|
|||
// TODO: Replace FragmentedPool with a better pool
|
|||
FragmentedPool<Component<T> > Components; |
|||
FragmentedPool<PooledComponent<T> > PooledComponents; |
|||
|
|||
protected: |
|||
typedef int FragmentedPoolIterator; |
|||
const int NULL_POOL_ITERATOR = -1; |
|||
|
|||
// Return the index of the first active data, or NULL_POOL_ITERATOR if the pool is inactive
|
|||
PooledComponent<T>* ActivePoolBegin(FragmentedPoolIterator& it) |
|||
{ |
|||
for (int i = 0; i < PooledComponents.GetPoolSize(); i++) |
|||
{ |
|||
FragmentedPoolData<PooledComponent<T> >* currentPooledComponent = |
|||
PooledComponents.GetActiveDataAtIndex(i); |
|||
|
|||
if (currentPooledComponent) |
|||
{ |
|||
it = i; |
|||
return ¤tPooledComponent->data; |
|||
} |
|||
} |
|||
|
|||
it = NULL_POOL_ITERATOR; |
|||
return nullptr; |
|||
} |
|||
|
|||
// Increments iterator until an active component is found.
|
|||
// Returns nullptr at end of list
|
|||
PooledComponent<T>* GetNextActivePooledComponent(FragmentedPoolIterator& it) |
|||
{ |
|||
it++; |
|||
for (; it < PooledComponents.GetPoolSize(); it++) |
|||
{ |
|||
FragmentedPoolData<PooledComponent<T> >* currentPooledComponent = |
|||
PooledComponents.GetActiveDataAtIndex(it); |
|||
|
|||
if (currentPooledComponent) |
|||
return ¤tPooledComponent->data; |
|||
} |
|||
|
|||
it = NULL_POOL_ITERATOR; |
|||
return nullptr; |
|||
} |
|||
|
|||
PooledComponent<T>* GetComponent(FragmentedPoolIterator& it) |
|||
{ |
|||
FragmentedPoolData<PooledComponent<T> >* pooledComponent = |
|||
PooledComponents.GetActiveDataAtIndex(it); |
|||
if (pooledComponent) |
|||
return &pooledComponent->data; |
|||
return nullptr; |
|||
} |
|||
|
|||
// This function is executed once for each entity which is being subscribed
|
|||
// This should only be used if your manager must do something per-component - otherwise, you
|
|||
// should write a custom solution
|
|||
// The component is already in the pool.
|
|||
virtual void SubscribeEntity(PooledComponent<T>& component) |
|||
{ |
|||
} |
|||
|
|||
// Do whatever your custom manager does for unsubscribing here. Don't implement if you don't
|
|||
// have to
|
|||
virtual void UnsubscribeEntity(PooledComponent<T>& component) |
|||
{ |
|||
} |
|||
|
|||
public: |
|||
PooledComponentManager(int poolSize) : PooledComponents(poolSize) |
|||
{ |
|||
} |
|||
|
|||
virtual ~PooledComponentManager() |
|||
{ |
|||
Reset(); |
|||
} |
|||
|
|||
// If the entity is already subscribed, the input component will be tossed out
|
|||
void SubscribeEntities(const std::vector<PooledComponent<T> >& components) |
|||
{ |
|||
for (typename std::vector<PooledComponent<T> >::const_iterator it = components.begin(); |
|||
it != components.end(); ++it) |
|||
{ |
|||
const PooledComponent<T> currentPooledComponent = (*it); |
|||
|
|||
// Make sure the Entity isn't already subscribed
|
|||
if (EntityListFindEntity(Subscribers, currentPooledComponent.entity)) |
|||
continue; |
|||
|
|||
FragmentedPoolData<PooledComponent<T> >* newPooledComponentPooled = |
|||
PooledComponents.GetNewData(); |
|||
|
|||
// Pool is full!
|
|||
// TODO: handle this elegantly
|
|||
assert(newPooledComponentPooled); |
|||
|
|||
newPooledComponentPooled->data = currentPooledComponent; |
|||
|
|||
Subscribers.push_back(currentPooledComponent.entity); |
|||
|
|||
SubscribeEntity(newPooledComponentPooled->data); |
|||
} |
|||
} |
|||
|
|||
virtual void SubscribeEntities(Component<T>& components) |
|||
virtual void UnsubscribeEntities(const EntityList& entities) |
|||
{ |
|||
EntityList entitiesToUnsubscribe; |
|||
EntityListAppendList(entitiesToUnsubscribe, entities); |
|||
|
|||
// Ensure that we only unsubscribe entities which are actually Subscribers
|
|||
EntityListRemoveUniqueEntitiesInSuspect(Subscribers, entitiesToUnsubscribe); |
|||
|
|||
for (EntityListConstIterator it = entitiesToUnsubscribe.begin(); |
|||
it != entitiesToUnsubscribe.end(); ++it) |
|||
{ |
|||
Entity currentEntity = (*it); |
|||
|
|||
for (int i = 0; i < PooledComponents.GetPoolSize(); i++) |
|||
{ |
|||
FragmentedPoolData<PooledComponent<T> >* currentPooledComponent = |
|||
PooledComponents.GetActiveDataAtIndex(i); |
|||
|
|||
if (currentPooledComponent && currentPooledComponent->data.entity == currentEntity) |
|||
{ |
|||
UnsubscribeEntity(currentPooledComponent->data); |
|||
PooledComponents.RemoveData(currentPooledComponent); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Remove all entities which were unsubscribed from the Subscribers list
|
|||
EntityListRemoveNonUniqueEntitiesInSuspect(entitiesToUnsubscribe, Subscribers); |
|||
} |
|||
|
|||
virtual void UnsubscribeEntities(EntityList& entities) |
|||
void Reset(void) |
|||
{ |
|||
PooledComponents.Clear(); |
|||
Subscribers.clear(); |
|||
} |
|||
}; |
|||
|
|||
|
Binary file not shown.
@ -0,0 +1,102 @@ |
|||
#include <vector> |
|||
#include <iostream> |
|||
#include <fstream> |
|||
|
|||
#include "flatbuffers/idl.h" |
|||
|
|||
#include "bogusSchema_generated.h" |
|||
|
|||
char *readSchema(const char *filename) |
|||
{ |
|||
std::ifstream inputFile; |
|||
|
|||
std::cout << "Reading in " << filename << "...\n"; |
|||
|
|||
// open with std::ios::ate so buffer pointer starts at end. tellg() tells us the size,
|
|||
// then we move the pointer back to the start and read in the entire file
|
|||
// This sucks and I should be ashamed to have written it
|
|||
inputFile.open(filename, std::ios::ate); |
|||
if (inputFile.is_open()) |
|||
{ |
|||
std::streampos size = inputFile.tellg(); |
|||
char *memblock = new char[size]; |
|||
inputFile.seekg(0, std::ios::beg); |
|||
inputFile.read(memblock, size); |
|||
inputFile.close(); |
|||
|
|||
std::cout << "Successfully read in " << filename << "\n\t(" << size << " bytes)\n"; |
|||
return memblock; |
|||
} |
|||
else |
|||
std::cout << "Failed to read in " << filename << "\n"; |
|||
|
|||
return nullptr; |
|||
} |
|||
|
|||
char *readBinary(const char *filename) |
|||
{ |
|||
std::ifstream inputFile; |
|||
|
|||
std::cout << "Reading in " << filename << "...\n"; |
|||
|
|||
// open with std::ios::ate so buffer pointer starts at end. tellg() tells us the size,
|
|||
// then we move the pointer back to the start and read in the entire file
|
|||
// This sucks and I should be ashamed to have written it
|
|||
inputFile.open(filename, std::ios::binary | std::ios::ate); |
|||
if (inputFile.is_open()) |
|||
{ |
|||
std::streampos size = inputFile.tellg(); |
|||
char *memblock = new char[size]; |
|||
inputFile.seekg(0, std::ios::beg); |
|||
inputFile.read(memblock, size); |
|||
inputFile.close(); |
|||
|
|||
std::cout << "Successfully read in " << filename << "\n\t(" << size << " bytes)\n"; |
|||
return memblock; |
|||
} |
|||
else |
|||
std::cout << "Failed to read in " << filename << "\n"; |
|||
|
|||
return nullptr; |
|||
} |
|||
|
|||
void testFlatbufferToJSON(void) |
|||
{ |
|||
const char *outputFilename = "Output.json"; |
|||
const char *flatbufferFilename = "SavedHelloForWrite.bin"; |
|||
const char *schemaFilename = "bogusSchema.flb"; |
|||
const char *includePaths = { |
|||
"/home/macoy/Development/code/repositories/galavant/src/experiments/flatbuffers"}; |
|||
|
|||
char *schemaBlock = readSchema(schemaFilename); |
|||
char *memblock = readBinary(flatbufferFilename); |
|||
|
|||
if (memblock && schemaBlock) |
|||
{ |
|||
const Galavant::Test::Hello *readInHello = Galavant::Test::GetHello(memblock); |
|||
// printHello(readInHello);
|
|||
|
|||
flatbuffers::Parser parser; |
|||
parser.Parse(schemaBlock, &includePaths, schemaFilename); |
|||
|
|||
std::string outputString = ""; |
|||
flatbuffers::GenerateText(parser, memblock, &outputString); |
|||
|
|||
std::cout << outputString << "\n"; |
|||
|
|||
//std::cout << "Generating text file...\n";
|
|||
//flatbuffers::GenerateTextFile(parser, memblock, outputFilename);
|
|||
|
|||
delete memblock; |
|||
delete schemaBlock; |
|||
} |
|||
} |
|||
|
|||
int main() |
|||
{ |
|||
std::cout << "Test Flatbuffers\n"; |
|||
|
|||
testFlatbufferToJSON(); |
|||
|
|||
return 1; |
|||
} |
@ -0,0 +1,29 @@ |
|||
#include <iostream> |
|||
|
|||
#include "../util/ObjectPool.hpp" |
|||
|
|||
int main() |
|||
{ |
|||
ObjectPool<int> testPool(100); |
|||
|
|||
for (int i = 0; i < testPool.GetSize(); i++) |
|||
{ |
|||
int* newData = testPool.GetNewData(); |
|||
if (newData) |
|||
*newData = i; |
|||
else |
|||
break; |
|||
} |
|||
|
|||
for (auto data:testPool) |
|||
{ |
|||
std::cout << data << "\n"; |
|||
} |
|||
|
|||
// for (PoolContainer<int>::iterator it = testPool.begin(); it != testPool.end(); ++it)
|
|||
// {
|
|||
// std::cout << (*it) << "\n";
|
|||
// }
|
|||
|
|||
return 1; |
|||
} |
@ -0,0 +1,107 @@ |
|||
#ifndef OBJECTPOOL_H__ |
|||
#define OBJECTPOOL_H__ |
|||
|
|||
#include <vector> |
|||
|
|||
typedef unsigned int ObjectPoolHandle; |
|||
|
|||
template <class T> |
|||
class ObjectPool |
|||
{ |
|||
public: |
|||
template <class R> |
|||
using PoolContainer = std::vector<R>; |
|||
|
|||
private: |
|||
typedef unsigned int PoolIndex; |
|||
// Provide a layer of indirection so ObjectPool can move data as it pleases
|
|||
// Fuck - what if you run out of handles? Don't allow that? What if pool resizes? Multiple
|
|||
// pools? How would handles work?
|
|||
typedef std::vector<PoolIndex> HandleTable; |
|||
|
|||
PoolContainer<T> Pool; |
|||
HandleTable Handles; |
|||
|
|||
unsigned int NextFreeData; |
|||
|
|||
ObjectPoolHandle CreateHandleInternal(PoolIndex index) |
|||
{ |
|||
Handles.push_back(index); |
|||
return Handles.size() - 1; |
|||
} |
|||
|
|||
public: |
|||
ObjectPool(unsigned int size) |
|||
{ |
|||
Pool.resize(size); |
|||
// Somewhat arbitrary size for handles
|
|||
Handles.resize(size); |
|||
NextFreeData = 0; |
|||
} |
|||
|
|||
typename PoolContainer<T>::iterator begin(void) |
|||
{ |
|||
return Pool.begin(); |
|||
} |
|||
|
|||
typename PoolContainer<T>::iterator end(void) |
|||
{ |
|||
// Only return the range of data which are actually active in the pool
|
|||
return Pool.begin() + NextFreeData; |
|||
} |
|||
|
|||
ObjectPoolHandle GetHandleFromIterator(typename PoolContainer<T>::iterator& it) |
|||
{ |
|||
return 0; // TODO
|
|||
} |
|||
|
|||
unsigned int GetSize(void) |
|||
{ |
|||
return Pool.size(); |
|||
} |
|||
|
|||
unsigned int GetNumActiveElements(void) |
|||
{ |
|||
// Because NextFreeData is the index of the free data nearest to the last active data, it is
|
|||
// equivalent to the number of active data
|
|||
return NextFreeData; |
|||
} |
|||
|
|||
T* GetNewData(void) |
|||
{ |
|||
T* newData = GetUnsafe(NextFreeData); |
|||
NextFreeData++; |
|||
return newData; |
|||
} |
|||
|
|||
// Use if you know you want to have the handle afterwards
|
|||
T* GetNewDataWithHandle(ObjectPoolHandle& handle) |
|||
{ |
|||
} |
|||
|
|||
// Returns nullptr if the data at handle isn't active
|
|||
// Note that this pointer should NOT be relied upon if you are removing other data
|
|||
T* Get(ObjectPoolHandle handle) |
|||
{ |
|||
if (handle < NextFreeData) |
|||
return GetUnsafe(handle); |
|||
return nullptr; |
|||
} |
|||
|
|||
// Don't perform bounds checking and don't make sure the data is active
|
|||
inline T* GetUnsafe(ObjectPoolHandle handle) |
|||
{ |
|||
return &Pool[handle]; |
|||
} |
|||
|
|||
// Remove the element and replace its space with the element at the end of the pool (swap)
|
|||
void Remove(ObjectPoolHandle handle) |
|||
{ |
|||
} |
|||
|
|||
void Remove(typename PoolContainer<T>::iterator& it) |
|||
{ |
|||
} |
|||
}; |
|||
|
|||
#endif /* end of include guard: OBJECTPOOL_H__ */ |
Loading…
Reference in new issue