1

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.

kaktusas2598
  • 641
  • 1
  • 7
  • 28
  • 1
    The garbage collector is the one that manages memory. Trying to `delete` your userdata manually means pulling the rug from under it (it will always result in a double `free`). In that sense, "let C++ take care of deleting it" cannot work. It's the Lua GC that deletes things, and the `__gc` metamethod is your chance to tell other code (such as your Entity Manager) that this pointer is going to be `free`d. – Lucas S. Dec 12 '22 at 14:44
  • But in my case LUA GC will not get called until I close the script, but I don't want to do that because each script will define multiple entities and so I want to make sure my Engine knows about any deleted entities and doesn't try to update/render them – kaktusas2598 Dec 12 '22 at 14:48
  • Then let the GC run normally and remove references to your values when you don't need them anymore. Then, `__gc` will be called eventually. And if you need a more direct/deterministic thing, you'll need an extra way to track "destroyed" entities that doesn't involve `free`ing their memory. – Lucas S. Dec 12 '22 at 15:21
  • So what you're saying it's fine to remove Entities from my vector in EntityManager and once Lua script gets closes just let take __gc take care of everything? – kaktusas2598 Dec 12 '22 at 15:26
  • From the perspective of memory management, you _have_ to let Lua take care of the memory. But for completeness, Lua values don't necessarily stick around until the entire Lua State is destroyed. Values only exist as long as there is at least one reference to it. E.g. if you did `testEntity = nil`, Lua's GC would delete the userdata shortly after. – Lucas S. Dec 12 '22 at 15:38
  • I just tested and yeah, ```__gc``` does work fine if I do ```testEntity = nil```, it also gets removed fine from EntityManager and does not get displayed on the screen anymore, but I feel like the actual memory is still used in C++ because destructor of Entity class was not called and if I call it explicitly program crashes – kaktusas2598 Dec 12 '22 at 15:45
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/250350/discussion-between-lucas-s-and-kaktusas2598). – Lucas S. Dec 12 '22 at 15:48

0 Answers0