How to manage vector of pointers in C++ where pointer is allocated by Lua

83 Views Asked by At

In the simple 2D Game Engine I am working. I want to define and script my entities using Lua and do all the heavy work in C++ (rendering, physics, etc.)

To do this I have basic Entity class:

class Entity {
    public:
        Entity() {
            alive = true;
            livingCount++;
        }

        void destroy() {
            alive = false;
        }

        bool isAlive() { return alive; }

        ~Entity(){
            // Gets called if I manually call delete entity in EntityManager or if I use smart pointers, but causes seg faults in either case
            std::cout << "Entity destructor called." << std::endl;
            livingCount--;
        }
    private:
        bool alive;
};

And the EntityManager singleton which groups all active Entities and is used in main game loop to refresh, update and render entities in this order, here is the EntityManager class:

class EntityManager {
        public:
            static EntityManager* Instance() {
                if(s_pInstance == 0) {
                    s_pInstance = new EntityManager();
                    return s_pInstance;
                }
                return s_pInstance;
            }
            // Main game loop goes through these and calls refresh(), then update() and finally render() on each frame
            void update(float deltaTime) {
                for (auto& e: entities)
                    e->update(deltaTime);
            }

            void render(float deltaTime) {
                for (auto& e: entities)
                    e->draw(deltaTime);

            }
            // This method causes segfault after entity gets destroyed, it doesn't matter if I use raw pointers or smart, I am still getting segfault here
            void refresh() {
                for (auto& e: entities) {
                    if (!e->isAlive()) {
                        entities.erase(std::remove(entities.begin(), entities.end(), e), entities.end());
                        // Uncommenting this also causes segfault, but if this is commented, Entity* is not freed from the memory, only gets removed from vector                        
                        // delete e;

                    }
                }
                // Tried doing the same using shared_ptr or even unique_ptr, but every time entity is destroyed this causes segfault as well
                // entities.erase(std::remove_if(std::begin(entities), std::end(entities),
                //     [](std::shared_ptr<Entity> &e) {
                //         return !e->isAlive();
                //     }),
                //     std::end(entities));
            }

            // Add new entity
            // This will become legacy, I am only using this to create entities in C++, but soon they will only be created by Lua and passed to this class
            Entity* addEntity() {
                Entity* entity = new Entity();
                entities.push_back(entity);
                return entity;
            }

            // Add existing entity: for lua
            // I have a feeling this method is not correct
            void addEntity(Entity* entity) {
                entities.push_back(entity);
            }

            const std::vector<Entity*>& getEntities() { return entities; }

        private:
            EntityManager() {}

            static EntityManager* s_pInstance;
            // Probably should use std::shared_ptr<Entity> instead
            // I tried this on Ubuntu and it does seem to solve memory problem and entities get deleted
            // but also getting segfault on Linux and I'm not sure if it's because of this class or
            // my use of unique pointers
            std::vector<Entity*> entities;
    };

Now everytime I want to create new Entity, I can do this from Lua script, because I have implemented __index and __gc metamethods for Entity, so this works perfectly:

testEntity = Entity.create()
testEntity:move(400, 400)
testEntity:scale(2, 2)
testEntity:addSprite("slime", "assets/sprite/slime.png", 32, 32)
testEntity:addAnimation("default", "0", "4")
testEntity:addCollider("enemy", 48, 48)
print("Slime ID: "..testEntity:id())

And finally, this is Lua binder for then we want to create new entity, notice how I pass pointer to EntityManager as well, because otherwise game will not update it:

 int Script::lua_createEntity(lua_State *L) {
    Entity* entity = (Entity*)lua_newuserdata(L, sizeof(Entity));
    // Placement new operator takes already allocated memory and calls constructor
    new (entity) Entity();
    // This Entity created in here must be added to EntityManager vector so that game knows about it
    EntityManager::Instance()->addEntity(entity);

    luaL_getmetatable(L, "EntityMetaTable");
    lua_setmetatable(L, -2);
    return 1;
}

And this method is bound to __gc metatable, but does not seem to get called at all, I'm not even sure if I want Lua to take care of this:

// Will be called once lua state is closed
int Script::lua_destroyEntity(lua_State *L) {
    Entity* entity = (Entity*)lua_touserdata(L, -1);
    std::cout << "Lua __gc for Entity called" << std::endl;
    entity->destroy();
    return 0;
}

Now, let's say I have an Entity which is also a Projectile, every time Projectile goes off screen, I call entity->destroy() on that Projectile and it's member "isAlive" is marked as "false". This is all done in C++, but projectile gets created by Lua script

So on the next frame, EntityManager refresh() method is invoked and that projectile gets removed correctly from the Vector, but if I try to delete it then to free the memory, it causes segfault, otherwise destructor never gets called.

As I said before, I have tried std::vectorstd::unique_ptr<Entity> and std::vectorstd::shared_ptr<Entity>, but that also causes segfault in EntityManager refresh method, which makes me think something fundamentally is wrong with how I am dealing with Entity lifetime.

__gc metamethod might not even be needed for me, as I want EntityManager to take care of all active Entities in the game, Lua will only be able to tell it which entity have been marked as destroyed and let C++ take care of deleting it.

0

There are 0 best solutions below