I've been refactoring the codebase for the engine I've been working on for the past several years, and have realized I may have made a grave architectural mistake. I started out by going back and adding const in areas that I had ignored prior just to get something working…which led me to eventually question the fact that I'm returning non-const pointers to private class members from a couple of my classes. Before I explain any further the following is a graph I made which outlines all Classes and their members where `App` is a singleton which encapsulates all logic and controls the loop: https://stashcube.com/vel-flowchart.pdf
So for example my Scene class contains `std::vector` members for all assets which are used for that scene instance. I then provide methods which dereference positions in those vectors to return raw pointers to individual assets. This allows for these values to be stored contiguously in memory. The raw pointers are then used by other elements (all of which would go out of scope prior to Scene).
However, this breaks encapsulation as these vectors are declared as private members within Scene as I do not wish for values to be added without going through a setter as additional logic is required for some at the time they are added into their corresponding containers. Below is my header file for a Scene:
namespace vel
{
class Scene
{
private:
std::vector<Shader> shaders;
std::vector<Mesh> meshes;
std::vector<Texture> textures;
std::vector<Material> materials;
std::vector<Animation> animations;
std::vector<Renderable> renderables;
std::vector<Armature> armatures;
std::vector<Stage> stages;
double animationTime;
protected:
size_t loadShader(std::string name, std::string vertFile, std::string fragFile);
void loadMesh(std::string path);
size_t loadTexture(std::string name, std::string type, std::string path, std::vector<std::string> mips = std::vector<std::string>());
void loadSceneConfig(std::string path);
size_t addMaterial(Material m);
size_t addRenderable(std::string name, size_t defaultShaderIndex, size_t crateMeshIndex, size_t crateMaterialIndex);
Stage& addStage();
Renderable getRenderable(std::string name);
Armature getArmature(std::string name);
Stage& getStage(size_t index);
public:
Scene();
~Scene();
virtual void load() = 0;
virtual void innerLoop(float deltaTime) = 0;
virtual void outerLoop(float frameTime, float renderLerpInterval) = 0;
virtual void postPhysics(float deltaTime);
bool loaded;
Mesh* addMesh(Mesh m);
Animation* addAnimation(Animation a);
Armature* addArmature(Armature a);
Shader* getShader(size_t si);
Shader* getShader(std::string shaderName);
Texture* getTexture(size_t textureIndex);
Material* getMaterial(size_t materialIndex);
Material* getMaterial(std::string materialName);
Mesh* getMesh(size_t index);
const std::vector<Mesh>& getMeshes() const;
Animation& getAnimation(size_t index);
std::vector<Animation>& getAnimations();
size_t getShaderIndex(std::string shaderName);
size_t getTextureIndex(std::string textureName);
size_t getMaterialIndex(std::string materialName);
size_t getMeshIndex(std::string meshName);
void updateAnimations(double runTime);
void draw(float alpha);
void stepPhysics(float delta);
void applyTransformations();
void processSensors();
};
}
Now other than this being a rather nasty code smell, everything in my existing source works, and I have come quite a long way. BUT now that I've realized what I've done, I'm trying to figure out what the best way would be to fix or restructure my existing design architecture so that I'm properly using const everywhere I need to be AND not breaking encapsulation, BUT keeping my existing OOP layout…but that might not be feasible?
I suppose if I made these raw pointer return values T* const
that would effectively correct the encapsulation issue, but the problem with that is that I may need to modify a Mesh
at some point by something other than Scene
.
Was hoping to get some peoples perspective on the situation. Maybe some opinions or options as to any better ways to structure what I'm doing. Any/All replies appreciated! ?