Created
December 6, 2021 03:08
-
-
Save Hexlord/caf1803da14bff20560a13309e62ffe3 to your computer and use it in GitHub Desktop.
Flecs wrappers (needs lots of testing)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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