Hazel
Hazel copied to clipboard
A simple approach to copy all scene components
/**
* @brief The copy registry works like this. (Just an idea of how it could be implemented)
* 1) you add a component to the an entity and call ComponentCopyRegistry::AddComponent<Component>...
* 2) when you need to copy all components, you can just call this: ComponentCopyRegistry::Invoke(dst,src,enttMap);
*
* P.S. I used similar approach in my engine for determining which components need to recieve OnUpdate callback etc...
*/
class ComponentCopyRegistry {
static std::vector<std::function<void(entt::registry& dst, entt::registry& src, std::unordered_map<UUID, entt::entity>& enttMap)>> copyFuncs;
// Add a component to the registry
template<typename T>
static void AddComponent() {
copyFuncs.push_back([&](entt::registry& dst, entt::registry& src, std::unordered_map<UUID, entt::entity>& enttMap)
{
auto view = src.view<Component>();
for (auto e : view) {
UUID uuid = src.get<IDComponent>(e).ID;
HZ_CORE_ASSERT(enttMap.find(uuid) != enttMap.end());
entt::entity dstEnttID = enttMap.at(uuid);
// Check if Entity has a component to copy
if (src.all_of<Component>(e)) {
auto& component = src.get<Component>(e);
dst.emplace_or_replace<Component>(dstEnttID, component);
}
}
});
}
// Call all functions to copy all components
static void Invoke(entt::registry& dst, entt::registry& src, std::unordered_map<UUID, entt::entity>& enttMap) {
for (auto& func : copyFuncs) {
func(dst, src, enttMap);
}
}
};
std::vector<std::function<void(entt::registry& dst, entt::registry& src, std::unordered_map<UUID, entt::entity>& enttMap)>> ComponentCopyRegistry::copyFuncs;
To be honest, I haven't tested this, but it should work.
The EnTT "way" to do this would be through storage and poly_storage and static polymorphism to access the registry's storage directly. Here is one adapted from my own code (mine has a bunch more functions, I cut it down to a minimal example):
#include <entt/entity/poly_storage.hpp>
template<typename... Type>
entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &);
template<typename Entity>
struct PolyStorage: entt::type_list_cat_t<
decltype(as_type_list(std::declval<entt::Storage<Entity>>())),
entt::type_list<
void(entt::basic_registry<Entity> &) const
>
> {
using entity_type = Entity;
using size_type = std::size_t;
template<typename Base>
struct type: entt::Storage<Entity>::template type<Base> {
static constexpr auto base = decltype(as_type_list(std::declval<entt::Storage<Entity>>()))::size;
/** Copy all entities into 'other'. WARNING: will crash if 'other' already contains data for any of the entities! */
void copy_to(entt::basic_registry<Entity>& other) const {
entt::poly_call<base + 0>(*this, other);
}
};
template<typename Type>
struct members {
static void copy_to(const Type &self, entt::basic_registry<entity_type> &other) {
const entt::sparse_set &base = self;
if constexpr(std::is_empty_v<typename Type::value_type>) {
other.template insert<typename Type::value_type>(base.rbegin(), base.rend());
} else {
other.template insert<typename Type::value_type>(base.rbegin(), base.rend(), self.rbegin());
}
}
};
template<typename Type>
using impl = entt::value_list_cat_t<
typename entt::Storage<Entity>::template impl<Type>,
entt::value_list<
&members<Type>::copy_to
>
>;
};
template<typename Entity>
struct entt::poly_storage_traits<Entity> {
using storage_type = entt::poly<PolyStorage<Entity>>;
};
Used like this:
void copyRegistry (const entt::registry& from, entt::registry& to)
{
// Force 'to' to be empty, to avoid the warning above
to = {};
// Make sure the entities exist in the 'to' registry
to.assign(from.data(), from.data() + from.size(), from.destroyed());
// Copy the components into the 'to' registry
from.visit([&from, &to](const auto info) {
from.storage(info)->copy_to(to);
});
}
There are two requirements to make this work. First, the above class needs to be included anywhere you use the entt::registry type, otherwise the compiler won't include the static polymorphism in the code and things will crash at runtime. So basically, put it in a header and include it anywhere EnTT's registry is included. Secondly, the components need a copy constructor (but if you just use POD structs, the default works fine).
The great thing about this approach is it works no matter what components you add. You don't need to change anything, no need for templating the components, it will just automatically work. Its also more efficient than using views and inserting new entities/components one by one, as it works with the underlying storage directly.
In my own code, I also have functions to copy single entities, allowing both overwriting existing components or skipping them. I use this so I can have a registry of "prototype" (or prefabs), from which I copy entities to the runtime registry when the prototype/prefab is instantiated. I also use this code to work across DLL/so boundaries. If I used templated components, I wouldn't be able to have DLLs create new components since the host templates wouldn't know about them.
But its not "simple", for sure. It uses EnTT's advanced features.