entt: struct Family bug with runtime shared library

Hello, it’s me again, I know I never bring good news but I find a case that annoys me with the family structure:

capture d ecran 2018-07-10 a 08 31 00

i got a static generated two times from family:

template <typename...>
    class Family
    {
        static std::atomic<std::size_t> identifier;

        template <typename...>
        static std::size_t family() ENTT_NOEXCEPT
        {
            static const std::size_t value = identifier.fetch_add(1);
            return value;
        }

    public:
        /*! @brief Unsigned integer type. */
        using family_type = std::size_t;

        /**
         * @brief Returns an unique identifier for the given type.
         * @return Statically generated unique identifier for the given type.
         */
        template <typename... Type>
        inline static family_type type() ENTT_NOEXCEPT
        {
            return family<std::decay_t<Type>...>();
        }
    };

    template<typename... Types>
    std::atomic<std::size_t> Family<Types...>::identifier{};

When you will use the dispatcher or the registry whatever that use family, it’s will use two times the family and take the wrong identifier, call the wrong callback and most of the time crash:

capture d ecran 2018-07-10 a 09 10 17 capture d ecran 2018-07-10 a 09 09 24 capture d ecran 2018-07-10 a 09 09 43

another approach for users who use plugins would be great, so many things in EnTT are thought for the plugins to work, unfortunately this part seems problematic

i got an approach that work through plugins and it’s suck but let me show (mixing static reflection and hashing + a vector) (bad performance etc, but it’s an approach) :

const auto fake_type = std::hash<std::string>()(Event::class_name());
        auto it = std::find_if(begin(id_memoisation ), end(id_memoisation ), [fake_type](size_t id) {return fake_type == id;});
        long type = 0;
        if (it == id_memoisation.end()) {
            id_memoisation .push_back(fake_type);
            type = std::distance(id_memoisation .begin(), std::prev(id_memoisation.end()));
        } else {
            type = std::distance(id_memoisation .begin(), it);
        }
        std::cout << Event::class_name() << ": " << type << " fake_type: " << fake_type << std::endl;

        if(!(type < wrappers.size())) {
            wrappers.resize(type + 1);
        }

        if(!wrappers[type]) {
            wrappers[type] = std::make_unique<SignalWrapper<Event>>();
        }

        return static_cast<SignalWrapper<Event> &>(*wrappers[type]);
    }

this snippet fix the problem and using the idx position of the memoisation vector, it’s suck but work’s.

honestly I do not think that with the solution to use a static currently we will manage to solve this problem.

i think we can mark it as bug.

Can we speak about some options that doesn’t ruin performance, and are acceptable for everyone ?

A solution to keep the static things would be something like: “please when you compile a shared library don’t generate the static you will have the symbol latter”.

But i’m not sure it’s possible honnestly

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 22 (20 by maintainers)

Most upvoted comments

objcopy -N "symbol" mylib.so

@skypjack I’ve thought about that ever since I first saw the family class. It’s not possible. It would be awesome if it were possible but it just isn’t. Below is my attempt at proving it is impossible 😄.

You would need a member function template of a class that returns the identifier of a type. The ID returned by calling this member function template with type T depends on whether the function has been called before. On the first call, the function will increment and return a counter. On subsequent calls, the function will not increment the counter. The function will instead return what it previously returned.

The behavior of the function depends on whether it has been called before. The function also needs to know what it previously returned. You could store this information in some kind of data structure but how do you access the data? You need an identifier for the type to know which element in a data structure to access to generate an identifier for a type. 🤔

A static variable in a function stores the information along with the function itself. So each instantiation of the function has an associated identifier and boolean. The function checks the boolean to determine whether to increment the counter or return the stored identifier.

int counter = 0;

template <typename T>
int identifier() {
  static const id = counter++;
  return id;
}

// instantiation with T = double
int identifier_double_id;
bool identifier_double_id_init = false;
int identifier_double() {
  if (!identifier_double_id_init) { // performed atomically
    identifier_double_id_init = true;
    identifier_double_id = counter++;
  }
  return identifier_double_id;
}

The function knows where to find the data because the address is fixed. If the address was not fixed, the function would need a type identifier to calculate the address of the data. You end up needing type identifiers to generate type identifiers. I wish this was possible but I just don’t see a way.

This fails across shared library boundaries on Windows because of the counter. The DLL and EXE do not share a counter. There are two separate counters and two separate instantiations for each type so it just falls apart.

I Will try the first approach and let you know if it’s work for me , i will let the issue open meanwhile this time, and tell you where i’am !