alecthomas / entityx

EntityX - A fast, type-safe C++ Entity-Component system
MIT License
2.21k stars 295 forks source link

family_counter_ issues when using EntityX as a shared library #225

Closed felippeduran closed 5 years ago

felippeduran commented 5 years ago

Hi,

I'm using EntityX as a shared library for a project that consists of a common code base of a game engine, an editor an a game demo. Initially, I was using it as a static library and ran into issues related to the System::family_counter_ being allocated into multiple addresses. Once I moved to a shared library this problem disappeared, however I'm facing another issue now.

Inside the game engine initialization, which is an implementation of entityx::EntityX, I call systems.add<deps::Dependency<ComponentB, ComponentA>>();, setup my systems and then call systems.configure(). Then, within the editor code (which uses the shared library), I create an entity as below, but it gives me an assertion:

entity.assign<ComponentB>();
entity.component<ComponentA>()->value = glm::vec3(0.0f, 15.0f, -20.0f);

Inspecting the issue a bit further, I found out that the Event::family_counter value set during the system/dependencies configuration changes when the event is created during this entity initialization.

Is there something I'm doing wrong? Should EntityX work properly in this case?

Thanks in advance!

alecthomas commented 5 years ago

What’s the assertion?

felippeduran commented 5 years ago

Thanks for the reply, @alecthomas!

The assertion says that the entity doesn’t have ComponentA. As the Event::family() changes between the time the dependency is created (within the shared library) and the time ComponentB is assigned to the entity (editor code), the event notification fails and EntityX doesn’t assign ComponentA automatically.

To be more descriptive about the code organisation, EntityX is compiled as a shared library. The engine code too, which uses the EntityX shared library, and the editor code uses both shared libraries.

alecthomas commented 5 years ago

Can you provide some sample code? Also are you sure the dwpendency is being added?

felippeduran commented 5 years ago

Hi, @alecthomas

Here's the sample code to reproduce the issue. It seems to be working properly when I compile it by hand (using clang or g++). The issue only occurs when I compile it as a XCode project/workspace, so I'm suspecting it might be related to some compiler configuration or XCode setup.

GameEngine.h:

#include "entityx/entityx.h"

struct ComponentA {
    int a;
};
struct ComponentB {
    int b;
};

class GameEngine : public entityx::EntityX {
public:
    // explicit GameEngine();
    // ~GameEngine();

    void initialize();
    void configure();
    int start();

    void update(entityx::TimeDelta dt);
};

struct DummySystem : public entityx::System<DummySystem> {
    void update(entityx::EntityManager &es, entityx::EventManager &events, entityx::TimeDelta dt) override {};
};

GameEngine.cpp:

#include "Engine.h"
#include "entityx/deps/Dependencies.h"

using namespace entityx;

void GameEngine::initialize() {
    systems.add<deps::Dependency<ComponentB, ComponentA>>();
    systems.add<DummySystem>();
}

void GameEngine::configure() {
    systems.configure();
}

main.cpp:

#include <iostream>
#include "entityx.h"
#include "Engine.h"

using namespace std;
using namespace entityx;

void setup();

GameEngine engine;

struct DummySystem2 : public entityx::System<DummySystem2> {
    void update(entityx::EntityManager &es, entityx::EventManager &events, entityx::TimeDelta dt) override {};
};

int main(int argc, const char *argv[])
{
    engine.initialize();
    engine.systems.add<DummySystem2>();
    engine.configure();

    setup();
    return 0;
}

void setup()
{
    Entity entity = engine.entities.create();
    entity.assign<ComponentB>();
    printf("Entity has ComponentA: %d\n", entity.has_component<ComponentA>());
    entity.component<ComponentA>()->a = 10; // This should throw an assert
}

The first two files belong to the engine and are compiled as a separate dynamic library. The third file, main.cpp, is compiled as an executable that links to the engine library. Both binaries link to an EntityX dynamic library.

felippeduran commented 5 years ago

Alright, so I finally managed to reproduce the issue outside XCode and fix it within the XCode project. The whole issue is caused by the compiler flag -fvisibility-inlines-hidden, which is enabled by default in XCode.

It seems that this flag was hiding the definition of the static variable family from each event template implementation (and maybe system) between shared objects. In the end, the issue was not related to the family_counter, but the family instead (which is now pretty obvious). I've also found an explanation of this behaviour in greater detail in this stackoverflow answer.

After I changed EntityX from a static library to a dynamic one, I was expecting that it would fix all problems related to duplicate memory addresses, but that was not the case and it bugged me!

Thanks for the help anyway, @alecthomas!

Hope this thread might be useful to other people in the future!

alecthomas commented 5 years ago

That is great to know, thanks. It seems frankly bizarre that statics would be inlined under any circumstances...