Skip to content

Instantly share code, notes, and snippets.

@Hexlord
Created December 6, 2021 03:08
Show Gist options
  • Save Hexlord/caf1803da14bff20560a13309e62ffe3 to your computer and use it in GitHub Desktop.
Save Hexlord/caf1803da14bff20560a13309e62ffe3 to your computer and use it in GitHub Desktop.
Flecs wrappers (needs lots of testing)
#include "Core.h"
#include "CoreReflection.h"
#include "Engine/Engine.h"
#include "Engine/SDL/SDL.h"
namespace SE {
namespace Impl {
void DestructImpl(entity Entity);
void DestructDeferredRecursive(entity Entity);
}
MCoreComponents::MCoreComponents(ecs &ECS) {
Module<MCoreComponents>(ECS);
// Spawn first, because wihout it we can not register components.
Spawn<Impl::CComponentMeta>(ECS);
Register<Impl::CComponentMeta>(ECS);
Register<Impl::CListenerMeta>(ECS);
Spawn<Impl::CListenerMeta>(ECS);
Register<Impl::CEventEmitter>(ECS);
Register<Impl::CDestructEventEmitter>(ECS);
Register<Impl::CDestructEventInitialEmitter>(ECS);
ECS.set_threads((int32)SE::Impl::ECSThreadCount);
Register<CThreadMarker>(ECS);
ECS.trigger<const Impl::CDestructEventInitialEmitter>("CDestructEventInitialEmitter OnSet")
.event(flecs::OnSet)
.each([](entity Entity, const Impl::CDestructEventInitialEmitter &Emitter) {
auto Target = entity(Entity.world(), Emitter.Target);
Check(Target);
// Debugf("Emitting initial destruct on %s", Target.name().c_str());
Impl::DestructDeferredRecursive(Target);
});
ECS.trigger<const Impl::CDestructEventEmitter>("CDestructEventEmitter OnSet")
.event(flecs::OnSet)
.each([](entity Entity, const Impl::CDestructEventEmitter &Emitter) {
auto Target = entity(Entity.world(), Emitter.Target);
Check(Target);
// Debugf("Emitting destruct on %s", Target.name().c_str());
Impl::DestructImpl(Target);
});
ECS.trigger<const Impl::CEventEmitter>("CEventEmitter OnSet").event(flecs::OnSet).each([](entity Entity, const Impl::CEventEmitter &Emitter) {
auto Target = entity_view(Entity.world(), Emitter.Target);
if (Target) {
auto ECS = Entity.world();
// Debugf("Emitting event on %s", Target.name().c_str());
Impl::EmitEventImpl(Target, Emitter.Event, Emitter.Payload);
}
Entity.destruct();
});
for (uint32 Index = 1, End = SE::Impl::ECSThreadCount; Index <= End; ++Index) {
ECS.entity().set<CThreadMarker>({Index});
}
}
void SetModuleEnabled(ecs &ECS, FStringView TypeName, bool Enabled) {
TFrameArray<entity> Entities;
FLongString Name = View("(ChildOf, ");
auto StringView = View(String::ReplaceAll<FFrameAllocator>(TypeName, View("::"), View(".")));
Name += StringView;
Name += View("), flecs.system.System, ");
if (Enabled) {
Name += View("Disabled");
} else {
Name += View("!Disabled");
}
while (true) {
entity Entity;
ECS.query_builder().expr(*Name).build().iter([&](iter &Iter) {
for (auto Index : Iter) {
Entity = Iter.entity(Index);
break;
}
});
if (Entity) {
if (Enabled) {
Entity.enable();
} else {
Entity.disable();
}
} else {
break;
}
}
for (auto Entity : Entities) {
Debugf("%s system %s", Enabled ? "Enabling" : "Disabling", Entity.name().c_str());
}
}
void ModuleDestroy(ecs &ECS, TFunction<void()> *Callback) {
ecs_atfini(
ECS.c_ptr(),
[](ecs_world_t *World, void *Context) {
(void)World;
((TFunction<void()> *)Context)();
},
(void *)Callback);
}
void ModuleDestroy(ecs &ECS, TFunction<void(ecs &)> *Callback) {
ecs_atfini(
ECS.c_ptr(),
[](ecs_world_t *World, void *Context) {
ecs ECS = ecs(World);
((TFunction<void(ecs &)> *)Context)(ECS);
},
(void *)Callback);
}
entity Instantiate(entity_view EntityView, entity_view Parent) {
return Reflection::InstantiateEntity(EntityView, Parent);
}
entity Clone(entity_view EntityView) {
return Reflection::CloneEntity(EntityView);
}
void Children(entity_view Entity, const TLambdaView<void(entity)> &Lambda) {
auto Filter = Entity.world().filter_builder().term(flecs::ChildOf, Entity).term(flecs::Prefab).oper(flecs::Optional).build();
Filter.each([&](entity Child) { Lambda(Child); });
}
void ApplyToChildren(entity_view Entity, const TLambdaView<void(entity)> &Lambda) {
TFrameArray<entity_index> Indices;
Children(Entity, [&](entity Child) { Indices.Add(Child); });
for (auto Index : Indices) {
Lambda(entity(Entity.world(), Index));
}
}
void ApplyToDeepestChildren(entity_view Entity, const TLambdaView<void(entity)> &Lambda) {
TFrameArray<entity_index> Indices;
Children(Entity, [&](entity Child) { Indices.Add(Child); });
if (!Indices.IsEmpty()) {
for (auto Index : Indices) {
ApplyToDeepestChildren(entity(Entity.world().c_ptr(), Index), Lambda);
}
} else {
Lambda(Entity.mut(Entity.world()));
}
}
entity Lookup(entity Entity, FStringView Name) {
if (auto EntityName = Entity.name()) {
if (FStringView(EntityName.c_str(), EntityName.size()) == Name) {
return Entity;
}
}
// TODO: Check if we still need this Optional thing instead of GetParent.
auto Filter = Entity.world().filter_builder().term(flecs::ChildOf, Entity).term(flecs::Prefab).oper(flecs::Optional).build();
entity Result;
Filter.iter([&](iter &Iter) {
for (auto Index : Iter) {
auto Child = Iter.entity(Index);
if (auto InnerResult = Lookup(Child, Name)) {
Result = InnerResult;
break;
}
}
});
return Result;
}
entity_view Lookup(entity_view Entity, FStringView Name) {
return Lookup(Entity.mut(Entity.world()), Name);
}
entity Lookup(ecs &ECS, FStringView Name) {
auto Result = Lookup(GetPersistentRoot(ECS).mut(ECS), Name);
if (!Result) {
Result = Lookup(GetTransientRoot(ECS).mut(ECS), Name);
}
return Result;
}
void PrintEntityHierarchyRecursive(entity_view Entity, uint32 Offset) {
{
FShortString Name;
if (Entity.name().size() > 0) {
Name = View(Entity.name().c_str());
} else {
Name = View("<Unnamed>");
}
Offset += Name.GetSize();
Outputf("%s\n", *Name);
}
TFrameArray<entity_view> ChildArray;
Children(Entity, [&](entity Entity) { ChildArray.Add(Entity); });
if (!ChildArray.IsEmpty()) {
auto OffsetString = String::Repeat<FFrameAllocator>(FStringView(" "), Offset);
for (uint32 Index = 0, End = ChildArray.GetSize(); Index < End; ++Index) {
FHeapString String = OffsetString;
if (Index == 0) {
String += FStringView("\\- ");
} else if (Index + 1 >= End) {
String += FStringView("x- ");
} else {
String += FStringView("|- ");
}
Outputf("%s", *String);
PrintEntityHierarchyRecursive(ChildArray[Index], Offset + 3);
}
}
}
void PrintEntityHierarchy(ecs &ECS) {
PrintEntityHierarchyRecursive(GetTransientRoot(ECS), 0);
PrintEntityHierarchyRecursive(GetPersistentRoot(ECS), 0);
}
entity GetParent(entity Entity) {
return Entity.get_object(flecs::ChildOf);
}
entity GetParent(entity_view Entity) {
return Entity.get_object(flecs::ChildOf);
}
entity GetNonRootParent(entity Entity) {
auto Parent = GetParent(Entity);
return IsRootEntity(Parent) ? entity() : Parent;
}
entity GetNonRootParent(entity_view Entity) {
auto Parent = GetParent(Entity);
return IsRootEntity(Parent) ? entity() : Parent;
}
entity_view GetTransientRoot(ecs &ECS) {
return Get<CTransientRoot>(ECS).Entity;
}
entity_view GetPersistentRoot(ecs &ECS) {
return Get<CPersistentRoot>(ECS).Entity;
}
bool IsRootEntity(entity Entity) {
auto ECS = Entity.world();
if (GetPersistentRoot(ECS) == Entity || GetTransientRoot(ECS) == Entity) {
return true;
}
return false;
}
bool IsRootEntity(entity_view Entity) {
return IsRootEntity(Entity.mut(Entity.world()));
}
bool IsMainThread() {
return Impl::MainThreadId == SDL_ThreadID();
}
bool IsDeferredContext(ecs &ECS) {
if(ECS.is_deferred()) {
return true;
} else {
Checkf(IsMainThread(), "We have to be deferred in multi-threaded systems");
return false;
}
}
bool IsDeferredContext(entity_view Entity) {
auto ECS = Entity.world();
return IsDeferredContext(ECS);
}
FFrameString EntityName(entity_view Entity) {
if (Entity.name()) {
return View(Entity.name().c_str());
} else {
return String::ToString(Entity.id());
}
}
FFrameString ComponentName(entity_view Component) {
FFrameString Result;
auto ECS = Component.world();
auto Id = Component.id();
Get(ECS, [&](const Impl::CComponentMeta &Meta) {
auto Ptr = Meta.ComponentEntityIdToName.Find(Id);
if(Ptr) {
Result = *Ptr;
} else {
Result = String::ToString(Id);
}
});
return Result;
}
namespace Impl {
void EmitEventImpl(entity_view Entity, entity_index Event, void *Payload) {
auto world = Entity.world().c_ptr();
auto record = ecs_record_find(world, Entity.id());
ecs_ids_t ids;
ids.count = 1;
ids.array = &Event;
ids.size = sizeof(Event);
ecs_event_desc_t desc;
memset(&desc, 0, sizeof(desc));
desc.event = Event;
desc.ids = &ids;
desc.table = record->table;
desc.offset = ECS_RECORD_TO_ROW(record->row);
desc.count = 1;
desc.observable = world;
desc.param = Payload;
ecs_emit(world, &desc);
}
FIterationMutations::FPair *FIterationMutations::GrabPair(entity_index Entity) {
for (auto &Pair : Pairs) {
if (Pair.Entity == Entity) {
return &Pair;
}
}
return &Pairs.Create();
}
void FIterationMutations::Add(entity Entity, entity_index Component) {
auto Pair = GrabPair(Entity);
if (Array::Find(Pair->MutatedComponents, Component) != Array::NotFound) {
Preventf(
"Component %s of entity %s was already mutated in this iteration",
*CComponentMeta::GetComponentNameUnsafe(entity(Entity.world().c_ptr(), Component)),
Entity.name().c_str());
}
Pair->MutatedComponents.Add(Component);
}
void FIterationMutations::Clear() {
Pairs.Clear();
}
void DestructImpl(entity Entity) {
Check(Entity);
Checkf(IsMainThread(), "Can't run this from multiple threads due to unsafe CListenerMeta access");
auto ECS = Entity.world();
// Debugf("Destructing %s", Entity.name().c_str());
Get(ECS, [&](const CListenerMeta &Meta) {
Meta.ApplyDestructEvents(Entity);
});
Entity.destruct();
}
void DestructImmediateRecursive(entity Entity) {
Check(Entity);
ApplyToChildren(Entity, [&](entity Child) { DestructImmediateRecursive(Child); });
DestructImpl(Entity);
}
void DestructDeferredRecursive(entity Entity) {
Check(Entity);
auto ECS = Entity.world();
ApplyToChildren(Entity, [&](entity Child) { DestructDeferredRecursive(Child); });
// Debugf("Setting Emitter on %s", Entity.name().c_str());
Entity.world().entity().set<CDestructEventEmitter>({Entity});
}
void DestructDeferred(entity Entity) {
Check(Entity);
Entity.world().entity().set<CDestructEventInitialEmitter>({Entity});
}
void CListenerMeta::ApplyDestructEventsPopulate(entity Entity, TArrayView<entity_index> Components, hash Hash, uint32 Bucket) const {
auto &EventIndices = ComponentsHashToDestructEventIndices.Create(Hash, Bucket);
if(IsDeferredContext(Entity)) {
for(uint32 Index = 0, End = DestructEventComponents.GetSize(); Index < End; ++Index) {
if(DestructEventComponents[Index].Matches(Components)) {
EventIndices.Add(Index);
Entity.add(DestructEvents[Index]);
}
}
} else {
// We can't use .add while iterating ComponentsHashToDestructEventIndices from singleton, it could be mutated inside the OnAdd listener.
TFrameArray<entity_index> Events;
for(uint32 Index = 0, End = DestructEventComponents.GetSize(); Index < End; ++Index) {
if(DestructEventComponents[Index].Matches(Components)) {
EventIndices.Add(Index);
Events.Add(DestructEvents[Index]);
}
}
for(auto Event : Events) {
Entity.add(Event);
}
}
}
void CListenerMeta::ApplyDestructEvents(entity Entity) const {
DestructedAnything = true;
auto Record = ecs_record_find(Entity.world().c_ptr(), Entity.id());
auto Table = Record->table;
auto Type = ecs_table_get_type(Table);
uint32 AllSize = Type->count;
auto AllComponents = TArrayView<entity_index>(ecs_vector_first(Type, ecs_id_t), AllSize);
TFrameArray<entity_index> Components;
Components.EnsureMaxSize(AllSize);
for(auto Component : AllComponents) {
if (!ECS_HAS_ROLE(Component, CASE) && !ECS_HAS_ROLE(Component, PAIR)) {
Components.Add(Component);
}
}
auto Size = Components.GetSize();
hash Hash = ComputeHash((const byte *)*Components, Size * sizeof(entity_index));
auto Bucket = ComponentsHashToDestructEventIndices.ComputeBucket(Hash);
if(auto Ptr = ComponentsHashToDestructEventIndices.Find(Hash, Bucket)) {
auto &EventIndices = *Ptr;
for(auto Index : EventIndices) {
Entity.add(DestructEvents[Index]);
}
} else {
ApplyDestructEventsPopulate(Entity, View(Components), Hash, Bucket);
}
}
} // namespace Impl
void Destruct(entity Entity) {
if (IsDeferredContext(Entity)) {
Impl::DestructDeferred(Entity);
} else {
Impl::DestructImmediateRecursive(Entity);
}
}
} // namespace SE
#pragma once
#include <flecs.h>
namespace SE {
using entity_index = flecs::entity_t;
using entity = flecs::entity;
using entity_view = flecs::entity_view;
using query = flecs::query<>;
using flecs::system;
using flecs::iter;
using flecs::column;
using ecs = flecs::world;
class MCoreComponents {
public:
MCoreComponents(ecs &ECS);
};
struct CThreadMarker {
uint32 Index;
};
// Children of PersistentRoot are serialized.
struct CPersistentRoot {
entity_view Entity;
};
// Entities marked as Transient are not serialized.
struct CTransient {};
// Children of TransientRoot are not serialized.
struct CTransientRoot {
entity_view Entity;
};
bool IsMainThread();
bool IsDeferredContext(ecs &ECS);
bool IsDeferredContext(entity_view Entity);
entity GetParent(entity Entity);
entity GetParent(entity_view Entity);
entity GetNonRootParent(entity Entity);
entity GetNonRootParent(entity_view Entity);
bool IsRootEntity(entity Entity);
bool IsRootEntity(entity_view Entity);
entity_view GetTransientRoot(ecs &ECS);
entity_view GetPersistentRoot(ecs &ECS);
void PrintEntityHierarchy(ecs &ECS);
// Iterates children, including prefabs.
void Children(entity_view Entity, const TLambdaView<void(entity Child)> &Lambda);
// First collects the entities, then applies the lambda to prevent table lock violation.
void ApplyToChildren(entity_view Entity, const TLambdaView<void(entity Child)> &Lambda);
// First collects the entities, then applies the lambda to prevent table lock violation.
// Lambda is only called on entities without children.
void ApplyToDeepestChildren(entity_view Entity, const TLambdaView<void(entity)> &Lambda);
entity Lookup(entity Entity, FStringView Name);
entity_view Lookup(entity_view Entity, FStringView Name);
entity Lookup(ecs &ECS, FStringView Name);
void SetModuleEnabled(ecs &ECS, FStringView TypeName, bool Enabled);
void ModuleDestroy(ecs &ECS, TFunction<void()> *Callback);
void ModuleDestroy(ecs &ECS, TFunction<void(ecs &)> *Callback);
entity Instantiate(entity_view EntityView, entity_view Parent);
entity Clone(entity_view EntityView);
FFrameString EntityName(entity_view Entity);
FFrameString ComponentName(entity_view Component);
template<typename T>
void SetModuleEnabled(ecs &ECS, bool Enabled) {
SetModuleEnabled(ECS, Meta::TypeName<T>(), Enabled);
}
template<typename T>
entity Component(ecs &ECS) {
return entity(ECS.c_ptr(), flecs::_::cpp_type<T>::id(ECS.c_ptr()));
}
template<typename T>
entity Component(const iter &Iter) {
return entity(Iter.world().c_ptr(), flecs::_::cpp_type<T>::id(Iter.world().c_ptr()));
}
template<typename T>
entity Component(entity_view Entity) {
return entity(Entity.world().c_ptr(), flecs::_::cpp_type<T>::id(Entity.world().c_ptr()));
}
// Used to split deferrment between entities.
namespace Impl {
struct CDestructEventEmitter {
entity_index Target;
};
// Used to wait for creation of deferred entities.
struct CDestructEventInitialEmitter {
entity_index Target;
};
struct CEventEmitter {
entity_index Target;
entity_index Event;
void *Payload;
};
struct CComponentMeta {
THashMap<hash, uint64, THasherBypass<hash>> ComponentNameHashToEntityId;
THashMap<uint64, FStringView, THasherBypass<uint64>> ComponentEntityIdToName;
static FFrameString GetComponentNameUnsafe(entity Entity) {
auto Meta = Entity.world().get<SE::Impl::CComponentMeta>();
if(Meta) {
auto Bucket = Meta->ComponentEntityIdToName.ComputeBucket(Entity);
if (Meta->ComponentEntityIdToName.Contains(Entity, Bucket)) {
return Meta->ComponentEntityIdToName.Get(Entity, Bucket);
}
}
return String::ToString(Entity);
}
};
// This struct is full of mutable stuff because we Get-access it for writes in ECS and expect it to work in single-threaded scenarios.
// We do this because we don't want any deferment, and we want performance (no emission).
struct CListenerMeta {
// We must assert that no two Listeners overlap with their EventComponents, it is a fundamental limitation of flecs - inability separate their deferment.
// 1 trigger = 1 deferment context.
THashSet<entity_index, THasherBypass<entity_index>> UsedEventComponents;
struct FDestructEventComponents {
THeapArray<entity_index> Components;
constexpr bool Matches(TArrayView<entity_index> InComponents) const {
auto InSize = InComponents.GetSize();
auto Size = Components.GetSize();
if(InSize < Size) {
return false;
}
uint32 Index = 0;
entity_index Component = Components[Index];
uint32 CurrentIndex = 0;
entity_index CurrentComponent;
while(true) {
CurrentComponent = InComponents[CurrentIndex];
++CurrentIndex;
if(CurrentComponent == Component) {
++Index;
if(Index == Size) {
return true;
}
Component = Components[Index];
} else if(CurrentIndex == InSize) {
return false;
}
}
return true;
}
};
// If destructed entity has all components of DestructEventComponents[i], it has to emit DestructEvents[i].
// Used to O(n*k) search matching destruct events when new combination of components with relations and roles removed is met.
mutable THeapArray<FDestructEventComponents> DestructEventComponents;
// Destruct event to emit if all corresponding DestructEventComponents components are present in an entity being destructed.
mutable THeapArray<entity_index> DestructEvents;
// Entity-side maintained for fast lookup of events corresponding to entity.
// Abuses the fact that Listeners are statically spawned before any of the entities.
mutable THashMap<hash, THeapArray<uint32>, THasherBypass<hash>> ComponentsHashToDestructEventIndices;
// Debug-only, used to assert that we do not populate listeners after destructing an entity.
mutable bool DestructedAnything = false;
// Debug-only hash collision prevention: check that your resulting hash is only ever matches a single combination of components.
// If a collision occurs, we have to offset component ids when creating them or change component hashing starting seed.
mutable THashMap<hash, THeapArray<entity_index>, THasherBypass<hash>> ComponentsHashToComponents;
void ApplyDestructEventsPopulate(entity Entity, TArrayView<entity_index> Components, hash Hash, uint32 Bucket) const;
void ApplyDestructEvents(entity Entity) const;
};
class FIterationMutations {
public:
void Add(entity Entity, entity_index Component);
void Clear();
private:
struct FPair {
entity_index Entity;
TFrameArray<entity_index> MutatedComponents;
};
// We must assert that no two Listeners overlap with their EventComponents, it is a fundamental limitation of flecs - inability separate their deferment.
// 1 trigger = 1 deferment context.
TFrameArray<FPair> Pairs;
FPair *GrabPair(entity_index Entity);
};
// Those components do not generate OnSet events, used for performance reason.
// Might break stuff!
inline THashSet<uint64, THasherBypass<hash>> OnSetIgnoringComponents;
// Debug only, used to assert that we do not accidentally mutate arbitrary entity in multi-threaded systems.
inline thread_local entity_index IteratedEntity = 0;
// Debug only, used to assert that we do not accidentally mutate same component multiple times.
// We put .Clear() inside the for loop so that it works faster for systems, and it does not matter for observers because those are always 1 row long.
inline thread_local FIterationMutations IterationMutations;
// Debug only, disabled inside systems that are not followed by sync point.
inline thread_local bool EventEmissionAllowed = true;
inline void SetIteratedEntity(entity_view Entity) {
if constexpr (SE_DEBUG) {
// Debugf("Switching iterated entity to %s", *EntityName(Entity));
Impl::IteratedEntity = Entity;
Impl::IterationMutations.Clear();
}
}
inline void ClearIteratedEntity() {
if constexpr (SE_DEBUG) {
Impl::IteratedEntity = 0;
}
}
inline void SetEventEmissionAllowed(bool Value) {
if constexpr (SE_DEBUG) {
Impl::EventEmissionAllowed = Value;
}
}
inline void ClearEventEmissionAllowed() {
if constexpr (SE_DEBUG) {
Impl::EventEmissionAllowed = true;
}
}
inline void AssertMutation(entity Entity, entity_index Component) {
if constexpr(SE_DEBUG) {
if(!IsMainThread()) {
auto AllowedEntity = entity(Entity.world().c_ptr(), Impl::IteratedEntity);
if(Entity != AllowedEntity) {
Preventf("Arbitrary mutation inside multi-threaded system is forbidden: mutating %s when %s is expected", *EntityName(Entity), *EntityName(AllowedEntity));
}
}
if(Component != 0) {
Checkf(Entity != 0, "No point in checking component mutation overlaps on world");
IterationMutations.Add(Entity, Component);
} else {
Checkf(Entity == 0, "Singleton components can only exist on the world");
}
}
}
template<typename T>
void AssertMutation(entity Entity) {
if constexpr(SE_DEBUG) {
auto ECS = Entity.world();
AssertMutation(Entity, Component<T>(ECS));
}
}
inline void AssertRead(entity_view Entity) {
if constexpr(SE_DEBUG) {
if(!IsMainThread()) {
auto AllowedEntity = entity(Entity.world().c_ptr(), Impl::IteratedEntity);
if(Entity != AllowedEntity) {
Preventf("Arbitrary read inside multi-threaded system is forbidden: reading %s when %s is expected", *EntityName(Entity), *EntityName(AllowedEntity));
}
}
}
}
inline uint32 ECSThreadCount;
inline unsigned long MainThreadId;
template<typename T, typename... Ts>
void RecursiveConcatComponentNames(FFrameString &Out) {
Out += Meta::TypeName<T>();
if constexpr (sizeof...(Ts) > 0) {
Out += View(", ");
RecursiveConcatComponentNames<Ts...>(Out);
}
}
inline void RecursiveConcatComponentNames(ecs &ECS, TArrayView<entity_index> Components, FFrameString &Out) {
auto Size = Components.GetSize();
Out += ComponentName(entity_view(ECS.c_ptr(), Components.First()));
if (Size > 1) {
Out += View(", ");
RecursiveConcatComponentNames(ECS, TArrayView<entity_index>(*Components + 1, Size - 1), Out);
}
}
template<uint32 Size, typename T, typename... Ts>
void RecursiveCollectComponentIds(ecs &ECS, TStackArray<entity_index, Size> &Out) {
Out.Add(Component<T>(ECS));
if constexpr (sizeof...(Ts) > 0) {
RecursiveCollectComponentIds<Size, Ts...>(ECS, Out);
}
}
void EmitEventImpl(entity_view Entity, entity_index Event, void *Payload);
} // namespace Impl
template<typename T>
class ref {
public:
ref() : m_world(nullptr), m_entity(0), m_ref() {
}
ref(entity_view Entity) : m_world(Entity.world().c_ptr()), m_entity(Entity.id()), m_ref() {
ecs_assert(flecs::_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL);
Impl::AssertRead(entity());
auto comp_id = flecs::_::cpp_type<T>::id(Entity.world().c_ptr());
ecs_get_ref_w_id(m_world, &m_ref, m_entity, comp_id);
}
ref(ecs &ECS) : m_world(ECS.c_ptr()), m_entity(((ecs &)ECS).singleton<T>().id()), m_ref() {
ecs_assert(flecs::_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL);
Impl::AssertRead(entity());
auto comp_id = flecs::_::cpp_type<T>::id(ECS.c_ptr());
ecs_get_ref_w_id(m_world, &m_ref, m_entity, comp_id);
}
ref(const iter &Iter) : ref(Iter.world()) {
}
template<typename Func>
void get(const Func &func) const {
Impl::AssertRead(entity());
const T *result = static_cast<const T *>(ecs_get_ref_w_id(m_world, &m_ref, m_entity, flecs::_::cpp_type<T>::id(m_world)));
ecs_assert(result, ECS_INVALID_PARAMETER, NULL);
func(*result);
}
const T *operator->() const {
Impl::AssertRead(entity());
const T *result = static_cast<const T *>(ecs_get_ref_w_id(m_world, &m_ref, m_entity, flecs::_::cpp_type<T>::id(m_world)));
ecs_assert(result, ECS_INVALID_PARAMETER, NULL);
return result;
}
explicit operator bool() const {
Impl::AssertRead(entity());
return static_cast<const T *>(ecs_get_ref_w_id(m_world, &m_ref, m_entity, flecs::_::cpp_type<T>::id(m_world)));
}
entity entity() const {
return flecs::entity(m_world, m_entity);
}
protected:
flecs::world_t *m_world;
flecs::entity_t m_entity;
mutable flecs::ref_t m_ref;
};
template<typename T>
class mut_ref : public ref<T> {
public:
mut_ref() : ref<T>() {
}
mut_ref(entity Entity) : ref<T>(Entity) {
Checkf(!IsDeferredContext(Entity), "Deferred context not yet supported");
}
mut_ref(ecs &ECS) : ref<T>(ECS) {
Checkf(!IsDeferredContext(ECS), "Deferred context not yet supported");
}
mut_ref(const iter &Iter) : mut_ref(Iter.world()) {
}
template<typename Func>
void set(const Func &func) {
auto comp_id = flecs::_::cpp_type<T>::id(this->m_world);
Impl::AssertMutation(entity(), comp_id);
T *result = (T *)(ecs_get_ref_w_id(this->m_world, &this->m_ref, this->m_entity, comp_id));
ecs_assert(result, ECS_INVALID_PARAMETER, NULL);
func(*result);
bool WithOnSet = !Impl::OnSetIgnoringComponents.Contains(comp_id);
if (WithOnSet) {
ecs_modified_id(this->m_world, this->m_entity, comp_id);
}
}
};
template<typename T>
void EmitEvent(entity_view Entity, T &&Event) {
Check(Impl::EventEmissionAllowed);
auto ECS = Entity.world();
if (IsDeferredContext(ECS)) {
void *Ptr = FFrameAllocator::Alloc(sizeof(T), Meta::DefaultAlignment);
if constexpr (!Meta::IsTriviallyDefaultConstructible<T>) {
Meta::Construct(*(T *)Ptr, Meta::Forward<T>(Event));
} else {
*(T *)Ptr = Meta::Forward<T>(Event);
}
ECS.entity().set<Impl::CEventEmitter>({Entity, Component<T>(ECS).id(), Ptr});
} else {
Impl::EmitEventImpl(Entity, Component<T>(ECS).id(), (void *)&Event);
}
}
template<typename TFunc>
void Scope(entity Entity, const TFunc &Func) {
Entity.world().scope(Entity, Func);
}
template<typename TFunc>
void Defer(ecs &ECS, const TFunc &Func) {
ECS.defer(Func);
}
template<typename T>
entity Module(ecs &ECS) {
Impl::AssertMutation({}, 0);
auto Entity = ECS.module<T>();
Debugf("Registered %u as module %s", Entity.id(), *FFrameString(Meta::TypeName<T>()));
return Entity;
}
template<typename T>
entity Import(ecs &ECS) {
Impl::AssertMutation({}, 0);
return ECS.import<T>();
}
template<typename T>
void IgnoreOnSet(ecs &ECS) {
Impl::AssertMutation({}, 0);
Impl::OnSetIgnoringComponents.Add(Component<T>(ECS));
}
template<typename T>
bool Has(iter &Iter) {
auto World = Iter.world();
return Iter.type().has(Component<T>(World));
}
template<typename T>
bool Has(entity Entity) {
return Entity.has<T>();
}
template<typename T>
bool Has(entity_view Entity) {
return Entity.has<T>();
}
template<typename T>
const T &Get(entity_view Entity) {
Impl::AssertRead(Entity);
auto Ptr = Entity.get<T>();
if constexpr (SE_DEBUG) {
if (!Ptr) {
Preventf("Entity %s does not have %s component", Entity.name().c_str(), *FFrameString(Meta::TypeName<T>()));
}
}
return *Ptr;
}
template<typename T>
const T *GetPtr(entity_view Entity) {
Impl::AssertRead(Entity);
return Entity.get<T>();
}
template<typename T>
const T &Get(ecs &ECS) {
Impl::AssertRead({});
auto Ptr = ECS.get<T>();
if constexpr (SE_DEBUG) {
if (!Ptr) {
Preventf("World does not have %s component", *FFrameString(Meta::TypeName<T>()));
}
}
return *Ptr;
}
template<typename T>
const T *GetPtr(ecs &ECS) {
Impl::AssertRead({});
return ECS.get<T>();
}
template<typename TFunc>
requires(Meta::IsCallable<TFunc>) void Get(entity_view Entity, const TFunc &Func) {
Impl::AssertRead(Entity);
if constexpr (SE_DEBUG) {
using T = Meta::TRemoveCVRef<Meta::TParameterPackFirstType<Meta::TFunctionParams<TFunc>>>;
if (!Entity.has<T>()) {
Preventf("Entity %s does not have %s component", Entity.name().c_str(), *FFrameString(Meta::TypeName<T>()));
}
}
Entity.get(Func);
}
template<typename TFunc>
requires(Meta::IsCallable<TFunc>) void Get(ecs &ECS, const TFunc &Func) {
Impl::AssertRead({});
if constexpr (SE_DEBUG) {
using T = Meta::TRemoveCVRef<Meta::TParameterPackFirstType<Meta::TFunctionParams<TFunc>>>;
if (!ECS.has<T>()) {
Preventf("World does not have %s component", *FFrameString(Meta::TypeName<T>()));
}
}
ECS.get(Func);
}
template<typename T, typename TFunc>
requires(Meta::IsCallable<TFunc>) void Get(const ref<T> &Ref, const TFunc &Func) {
Impl::AssertRead(Ref.entity());
if constexpr (SE_DEBUG) {
using TFirstArg = Meta::TRemoveCVRef<Meta::TParameterPackFirstType<Meta::TFunctionParams<TFunc>>>;
StaticCheck((Meta::IsSame<TFirstArg, T>));
if (!Ref) {
Preventf("Entity %s does not have %s component", Ref.entity().name().c_str(), *FFrameString(Meta::TypeName<T>()));
}
}
Ref.get(Func);
}
template<typename T>
T &Set(entity, const T &) {
Preventf("Use function overload instead, it the only way to never forget writing .modified");
return *(T *)0;
}
template<typename T>
T &Set(ecs &, const T &) {
Preventf("Use function overload instead, it the only way to never forget writing .modified");
return *(T *)0;
}
template<typename T>
T &GetMut(entity) {
Preventf("Use function overload instead, it the only way to never forget writing .modified");
return *(T *)0;
}
template<typename T>
T &GetMut(ecs &) {
Preventf("Use function overload instead, it the only way to never forget writing .modified");
return *(T *)0;
}
template<typename T>
requires(!Meta::IsCallable<T>) void Set(entity Entity, T &&Value) {
Impl::AssertMutation<T>(Entity);
if constexpr (SE_DEBUG) {
if (!Entity.has<T>()) {
Preventf("Entity %s does not have %s component", Entity.name().c_str(), *FFrameString(Meta::TypeName<T>()));
}
}
Entity.set<T>(Meta::Forward<T>(Value));
}
template<typename T>
requires(!Meta::IsCallable<T>) void Set(ecs &ECS, T &&Value) {
Impl::AssertMutation({}, 0);
if constexpr (SE_DEBUG) {
if (!ECS.has<T>()) {
Preventf("World does not have %s component", *FFrameString(Meta::TypeName<T>()));
}
}
ECS.set<T>(Meta::Forward<T>(Value));
}
template<typename T>
requires(!Meta::IsCallable<T>) void SetOrSpawn(entity Entity, T &&Value) {
Impl::AssertMutation<T>(Entity);
Entity.set<T>(Meta::Forward<T>(Value));
}
template<typename T>
requires(!Meta::IsCallable<T>) void SetOrSpawn(ecs &ECS, T &&Value) {
Impl::AssertMutation({}, 0);
ECS.set<T>(Meta::Forward<T>(Value));
}
template<typename TFunc>
requires(Meta::IsCallable<TFunc>) void Set(entity Entity, const TFunc &Func) {
using T = Meta::TRemoveCVRef<Meta::TParameterPackFirstType<Meta::TFunctionParams<TFunc>>>;
Impl::AssertMutation<T>(Entity);
if constexpr (SE_DEBUG) {
if (!Entity.has<T>()) {
Preventf("Entity %s does not have %s component", Entity.name().c_str(), *FFrameString(Meta::TypeName<T>()));
}
}
auto ECS = Entity.world();
bool WithOnSet = !Impl::OnSetIgnoringComponents.Contains(Component<T>(ECS));
if (WithOnSet) {
Entity.set(Func);
} else {
Func(*Entity.get_mut<T>());
}
}
template<typename TFunc>
requires(Meta::IsCallable<TFunc>) void Set(ecs &ECS, const TFunc &Func) {
using T = typename Meta::TRemoveCVRef<Meta::TParameterPackFirstType<Meta::TFunctionParams<TFunc>>>;
Impl::AssertMutation({}, 0);
if constexpr (SE_DEBUG) {
if (!ECS.has<T>()) {
Preventf("World does not have %s component", *FFrameString(Meta::TypeName<T>()));
}
}
ECS.set(Func);
}
template<typename T, typename TFunc>
requires(Meta::IsCallable<TFunc>) void Set(mut_ref<T> &Ref, const TFunc &Func) {
Impl::AssertMutation<T>(Ref.entity());
if constexpr (SE_DEBUG) {
using TFirstArg = Meta::TRemoveCVRef<Meta::TParameterPackFirstType<Meta::TFunctionParams<TFunc>>>;
StaticCheck((Meta::IsSame<TFirstArg, T>));
if (!Ref) {
Preventf("Entity %s does not have %s component", Ref.entity().name().c_str(), *FFrameString(Meta::TypeName<T>()));
}
}
Ref.set(Func);
}
inline void RegisterBuiltin(ecs &ECS, entity_index Component, FStringView Name) {
Impl::AssertMutation({}, 0);
Set(ECS, [&](Impl::CComponentMeta &Meta) {
Meta.ComponentNameHashToEntityId.Add(ComputeHash(Name), Component);
Meta.ComponentEntityIdToName.Add(Component, Name);
});
Debugf("Registered %u as built-in component %s", Component, *FFrameString(Name));
}
template<typename T>
entity Register(ecs &ECS) {
Impl::AssertMutation({}, 0);
ecs_entity_t prev = ecs_set_scope(ECS.c_ptr(), 0);
auto Entity = ECS.use<T>();
Set(ECS, [&](Impl::CComponentMeta &Meta) {
auto Name = Meta::TypeName<T>();
Meta.ComponentNameHashToEntityId.Add(ComputeHash(Name), Entity.id());
Meta.ComponentEntityIdToName.Add(Entity.id(), Name);
});
Debugf("Registered %u as component %s", Entity.id(), *FFrameString(Meta::TypeName<T>()));
ecs_set_scope(ECS.c_ptr(), prev);
return Entity;
}
template<typename T>
void Spawn(entity Entity) {
if (Entity.has<T>()) {
Preventf("Entity %s already has %s component", Entity.name().c_str(), *FFrameString(Meta::TypeName<T>()));
}
Entity.add<T>();
}
template<typename T>
void TrySpawn(entity Entity) {
Entity.add<T>();
}
template<typename T>
void Spawn(ecs &ECS) {
if (ECS.has<T>()) {
Preventf("World already has %s component", *FFrameString(Meta::TypeName<T>()));
}
ECS.add<T>();
}
template<typename T>
void TrySpawn(ecs &ECS) {
ECS.add<T>();
}
template<typename T>
void Remove(entity Entity) {
if (!Entity.has<T>()) {
Preventf("Entity %s does not has %s component", Entity.name().c_str(), *FFrameString(Meta::TypeName<T>()));
}
Entity.remove<T>();
}
template<typename T>
void TryRemove(entity Entity) {
Entity.remove<T>();
}
template<typename T>
void Remove(ecs &ECS) {
if (!ECS.has<T>()) {
Preventf("World does not has %s component", *FFrameString(Meta::TypeName<T>()));
}
ECS.remove<T>();
}
template<typename T>
void TryRemove(ecs &ECS) {
ECS.remove<T>();
}
template<typename... TArgs, bool AllowEventEmission = false, typename TFunc>
void System(ecs &ECS, TFunc &&Func) {
Impl::AssertMutation({}, 0);
using TFuncParams = Meta::TFunctionParams<TFunc>;
constexpr uint32 TermCount = sizeof...(TArgs);
constexpr uint32 FuncArgCount = TFuncParams::Size;
StaticCheckf(FuncArgCount <= 1 || FuncArgCount == TermCount + 1, "Either all or none of term params must be present");
FFrameString Name = View("(");
Impl::RecursiveConcatComponentNames<TArgs...>(Name);
Name += View(") System ");
{
auto Builder = ECS.system<TArgs...>().multi_threaded();
Name += String::ToString(Builder.id());
if constexpr (FuncArgCount > 0) {
if constexpr (FuncArgCount == 1) {
// Callback only receives one parameter: entity / iter &
if constexpr (Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, entity>) {
auto System = Builder.iter([Callback = TLambda<void(entity)>(Meta::Forward<TFunc>(Func))](iter &Iter) {
Impl::SetEventEmissionAllowed(AllowEventEmission);
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
Impl::SetIteratedEntity(Entity);
Callback(Entity);
}
Impl::ClearIteratedEntity();
Impl::ClearEventEmissionAllowed();
});
System.set_name(*Name);
} else {
StaticCheck((Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, iter &>));
auto System = Builder.iter([Callback = TLambda<void(iter &)>(Meta::Forward<TFunc>(Func))](iter &Iter) {
Impl::SetEventEmissionAllowed(AllowEventEmission);
if constexpr (SE_DEBUG) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
Impl::SetIteratedEntity(Entity);
// TODO: offset iter and set its count to 1.
Prevent();
}
} else {
Callback(Iter);
}
Impl::ClearIteratedEntity();
Impl::ClearEventEmissionAllowed();
});
System.set_name(*Name);
}
} else {
// Callback receives all the parameters, starting with: entity / iter &
if constexpr (Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, entity>) {
auto System = Builder.iter(
[Callback = TLambda<void(entity, TArgs & ...)>(Meta::Forward<TFunc>(Func))](iter &Iter, TArgs *...Args) {
Impl::SetEventEmissionAllowed(AllowEventEmission);
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
Impl::SetIteratedEntity(Entity);
Callback(Entity, Args[Index]...);
}
Impl::ClearIteratedEntity();
Impl::ClearEventEmissionAllowed();
});
System.set_name(*Name);
} else {
StaticCheck((Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, iter &>));
auto System = Builder.iter(
[Callback = TLambda<void(iter &, TArgs * ...)>(Meta::Forward<TFunc>(Func))](iter &Iter, TArgs *...Args) {
Impl::SetEventEmissionAllowed(AllowEventEmission);
if constexpr (SE_DEBUG) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
Impl::SetIteratedEntity(Entity);
// TODO: offset iter and set its count to 1.
Prevent();
}
} else {
Callback(Iter, Args...);
}
Impl::ClearIteratedEntity();
Impl::ClearEventEmissionAllowed();
});
System.set_name(*Name);
}
}
} else {
auto System = Builder.iter([Callback = TLambda<void()>(Meta::Forward<TFunc>(Func))](iter &Iter) {
Impl::SetEventEmissionAllowed(AllowEventEmission);
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
Impl::SetIteratedEntity(Entity);
Callback();
}
Impl::ClearIteratedEntity();
Impl::ClearEventEmissionAllowed();
});
System.set_name(*Name);
}
}
}
namespace Impl {
template<typename... TArgs, typename TFunc>
void Listener(ecs &ECS, TFunc &&Func, entity_index Event, FStringView EventName, entity_index EventComponent) {
Impl::AssertMutation({}, 0);
using TFuncParams = Meta::TFunctionParams<TFunc>;
constexpr uint32 TermCount = sizeof...(TArgs);
constexpr uint32 FuncArgCount = TFuncParams::Size;
StaticCheckf(TermCount > 0, "No components to listen for");
StaticCheckf(FuncArgCount <= 1 || FuncArgCount == TermCount + 1, "Either all or none of signature params must be present");
FFrameString Name = View("(");
Impl::RecursiveConcatComponentNames<TArgs...>(Name);
Name += View(") ");
Name += EventName;
Name += View(" Listener ");
if constexpr (SE_DEBUG) {
TStackArray<entity_index, TermCount> TermComponentIds;
Impl::RecursiveCollectComponentIds<TermCount, TArgs...>(ECS, TermComponentIds);
if (EventComponent != 0) {
Checkf(Array::Find(TermComponentIds, EventComponent) != Array::NotFound, "EventComponent not present in listener signature");
Set(ECS, [&](Impl::CListenerMeta &Meta) {
auto Bucket = Meta.UsedEventComponents.ComputeBucket(EventComponent);
if(Meta.UsedEventComponents.Contains(EventComponent, Bucket)) {
Preventf("Component %s was already used in listeners as EventComponent", *CComponentMeta::GetComponentNameUnsafe(entity(ECS.c_ptr(), EventComponent)));
}
Meta.UsedEventComponents.Add(EventComponent, Bucket);
});
} else {
Set(ECS, [&](Impl::CListenerMeta &Meta) {
for(auto Component : TermComponentIds) {
auto Bucket = Meta.UsedEventComponents.ComputeBucket(Component);
if(Meta.UsedEventComponents.Contains(Component, Bucket)) {
Preventf("Component %s was already used in listeners as EventComponent", *CComponentMeta::GetComponentNameUnsafe(entity(ECS.c_ptr(), Component)));
}
Meta.UsedEventComponents.Add(Component, Bucket);
}
});
}
}
if constexpr (TermCount == 1) {
auto Builder = ECS.trigger<TArgs...>().event(Event);
Name += String::ToString(Builder.id());
if constexpr (FuncArgCount > 0) {
if constexpr (FuncArgCount == 1) {
// Callback only receives one parameter: entity / iter &
if constexpr (Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, entity>) {
auto Trigger = Builder.iter([EventComponent, Callback = TLambda<void(entity)>(Meta::Forward<TFunc>(Func))](iter &Iter) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
Callback(Entity);
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
} else {
StaticCheck((Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, iter &>));
auto Trigger = Builder.iter([EventComponent, Callback = TLambda<void(iter &)>(Meta::Forward<TFunc>(Func))](iter &Iter) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
if constexpr (SE_DEBUG) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
// TODO: offset iter and set its count to 1.
Prevent();
}
} else {
Callback(Iter);
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
}
} else {
// Callback receives all the parameters, starting with: entity / iter &
if constexpr (Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, entity>) {
auto Trigger = Builder.iter(
[EventComponent, Callback = TLambda<void(entity, TArgs & ...)>(Meta::Forward<TFunc>(Func))](iter &Iter, TArgs *...Args) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
Callback(Entity, Args[Index]...);
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
} else {
StaticCheck((Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, iter &>));
auto Trigger = Builder.iter(
[EventComponent, Callback = TLambda<void(iter &, TArgs & ...)>(Meta::Forward<TFunc>(Func))](iter &Iter, TArgs *...Args) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
if constexpr (SE_DEBUG) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
// TODO: offset iter and set its count to 1.
Prevent();
}
} else {
Callback(Iter, Args...);
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
}
}
} else {
auto Trigger = Builder.iter([EventComponent, Callback = TLambda<void()>(Meta::Forward<TFunc>(Func))](iter &Iter) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
Callback();
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
}
} else {
auto Builder = ECS.observer<TArgs...>().event(Event);
Name += String::ToString(Builder.id());
if constexpr (FuncArgCount > 0) {
if constexpr (FuncArgCount == 1) {
// Callback only receives one parameter: entity / iter &
if constexpr (Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, entity>) {
auto Trigger = Builder.iter([EventComponent, Callback = TLambda<void(entity)>(Meta::Forward<TFunc>(Func))](iter &Iter) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
Callback(Entity);
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
} else {
StaticCheck((Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, iter &>));
auto Trigger = Builder.iter([EventComponent, Callback = TLambda<void(iter &)>(Meta::Forward<TFunc>(Func))](iter &Iter) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
if constexpr (SE_DEBUG) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
// TODO: offset iter and set its count to 1.
Prevent();
}
} else {
Callback(Iter);
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
}
} else {
// Callback receives all the parameters, starting with: entity / iter &
if constexpr (Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, entity>) {
auto Trigger = Builder.iter(
[EventComponent, Callback = TLambda<void(entity, TArgs & ...)>(Meta::Forward<TFunc>(Func))](iter &Iter, TArgs *...Args) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
Callback(Entity, Args[Index]...);
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
} else {
StaticCheck((Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, iter &>));
auto Trigger = Builder.iter(
[EventComponent, Callback = TLambda<void(iter &, TArgs * ...)>(Meta::Forward<TFunc>(Func))](iter &Iter, TArgs *...Args) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
if constexpr (SE_DEBUG) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
// TODO: offset iter and set its count to 1.
Prevent();
}
} else {
Callback(Iter, Args...);
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
}
}
} else {
auto Trigger = Builder.iter([EventComponent, Callback = TLambda<void()>(Meta::Forward<TFunc>(Func))](iter &Iter) {
if (EventComponent == 0 || Iter.event_id() == EventComponent) {
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
SetIteratedEntity(Entity);
Callback();
}
Impl::ClearIteratedEntity();
}
});
Trigger.set_name(*Name);
}
}
}
} // namespace Impl
// Callback is expected to be void(entity, const CEvent &)
template<typename... TArgs, typename TFunc>
void EventListener(ecs &ECS, TFunc &&Func) {
Impl::AssertMutation({}, 0);
using TFuncParams = Meta::TFunctionParams<TFunc>;
StaticCheckf(TFuncParams::Size == 2, "Callback is expected to be void(entity, const <event_type> &)");
StaticCheckf(Meta::IsSame<Meta::TParameterPackFirstType<TFuncParams>, entity>, "Callback is expected to be void(entity, const <event_type> &)");
using TSecondParam = Meta::TRemoveCVRef<Meta::TParameterPackSecondType<TFuncParams>>;
auto ComponentId = Component<TSecondParam>(ECS);
if constexpr (SE_DEBUG) {
Set(ECS, [&](Impl::CListenerMeta &Meta) {
auto Bucket = Meta.UsedEventComponents.ComputeBucket(ComponentId);
if(Meta.UsedEventComponents.Contains(ComponentId, Bucket)) {
Preventf("Component %s was already used in listeners as EventComponent", *Impl::CComponentMeta::GetComponentNameUnsafe(entity(ECS.c_ptr(), ComponentId)));
}
Meta.UsedEventComponents.Add(ComponentId, Bucket);
});
}
FLongString String = Meta::TypeName<TSecondParam>();
String += View(" Event Listener ");
auto Trigger = ECS.trigger<TSecondParam>()
.event(Component<TSecondParam>(ECS))
.iter([Callback = TLambda<void(entity, TSecondParam &)>(Meta::Forward<TFunc>(Func))](iter &Iter) {
if ((... && Has<TArgs>(Iter))) {
auto &Event = *(TSecondParam *)Iter.param();
for (auto Index : Iter) {
auto Entity = Iter.entity(Index);
Impl::SetIteratedEntity(Entity);
Callback(Entity, Event);
}
Impl::ClearIteratedEntity();
}
});
String += String::ToString(Trigger.id());
Trigger.set_name(*String);
}
template<typename... TArgs, typename TFunc>
void OnAddListener(ecs &ECS, TFunc &&Func, entity_index EventComponent = 0) {
Impl::AssertMutation({}, 0);
Impl::Listener<TArgs...>(ECS, Meta::Forward<TFunc>(Func), flecs::OnAdd, View("OnAdd"), EventComponent);
}
template<typename... TArgs, typename TFunc>
void OnSetListener(ecs &ECS, TFunc &&Func, entity_index EventComponent = 0) {
Impl::AssertMutation({}, 0);
Impl::Listener<TArgs...>(ECS, Meta::Forward<TFunc>(Func), flecs::OnSet, View("OnSet"), EventComponent);
}
// Expected callback is void(entity)
template<typename... TArgs, typename TFunc>
void OnDestructListener(ecs &ECS, TFunc &&Func, entity_index EventComponent = 0) {
constexpr uint32 TermCount = sizeof...(TArgs);
StaticCheckf(TermCount > 0, "At least one term is required");
Impl::AssertMutation({}, 0);
Checkf(EventComponent == 0, "EventComponent is not supported for OnDestruct as we are always reacting to CDestructEvent");
Checkf(IsMainThread(), "Can't run this from multiple threads due to unsafe CListenerMeta access");
TStackArray<entity_index, TermCount> TermComponentIds;
Impl::RecursiveCollectComponentIds<TermCount, TArgs...>(ECS, TermComponentIds);
if constexpr (SE_DEBUG) {
for(auto Component : TermComponentIds) {
if(ECS_HAS_ROLE(Component, CASE) || ECS_HAS_ROLE(Component, PAIR)) {
Preventf("Component %s is not a valid component to be present in OnDestructListener terms", *ComponentName(entity_view(ECS.c_ptr(), Component)));
}
}
}
Array::SortAscending(TermComponentIds);
entity Event;
auto ComponentsView = View(TermComponentIds);
FFrameString Names;
Impl::RecursiveConcatComponentNames(ECS, ComponentsView, Names);
Get(ECS, [&](const Impl::CListenerMeta &Meta) {
if constexpr (SE_DEBUG) {
Checkf(!Meta.DestructedAnything, "Registering OnDestructListener-s after emitting destruct events is forbidden");
}
hash Hash = ComputeHash((const byte *)*TermComponentIds, TermComponentIds.GetSize() * sizeof(entity_index));
// Check for hash collisions & identical terms.
if constexpr (SE_DEBUG) {
auto Bucket = Meta.ComponentsHashToComponents.ComputeBucket(Hash);
if(!Meta.ComponentsHashToComponents.Contains(Hash, Bucket)) {
Meta.ComponentsHashToComponents.Add(Hash, ComponentsView);
} else {
auto MetaComponentsView = View(Meta.ComponentsHashToComponents.Get(Hash, Bucket));
if(MetaComponentsView != ComponentsView) {
FFrameString MetaNames;
Impl::RecursiveConcatComponentNames(ECS, MetaComponentsView, MetaNames);
Preventf("Hash collision detected between (%s) and (%s): both equal to %u", *MetaNames, *Names, Hash);
} else {
Preventf("Listener with identical terms (%s) already exists", *Names);
}
}
}
Event = ECS.entity();
Meta.DestructEventComponents.Add({TermComponentIds});
Meta.DestructEvents.Add(Event);
});
FFrameString Name = View("(");
Name += Names;
Name += View(") OnDestruct Listener ");
auto Trigger = ECS.trigger(nullptr, Event)
.event(flecs::OnAdd)
.iter([Callback = TLambda<void(entity)>(Meta::Forward<TFunc>(Func))](iter &Iter) {
Check(Iter.count() == 1);
Check((... && Has<TArgs>(Iter)));
auto Entity = Iter.entity(0);
Impl::SetIteratedEntity(Entity);
Callback(Entity);
Impl::ClearIteratedEntity();
});
Name += String::ToString(Trigger.id());
Debugf("Registered %s", *Name);
Trigger.set_name(*Name);
}
void Destruct(entity Entity);
} // namespace SE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment