Hey,
I've ported the EntitasCSharp/Unity3D MatchOne example to EnTT/Unreal Engine 4, which can be found here under MatchOneEntt. It's a really small example and I would love to get feedback whether the places using EnTT could be improved in an idiomatic way.
In general it is quite pleasant to work with EnTT, though I do miss some features coming from Entitas-CSharp. Things like reactive systems that get triggered when a component gets added/removed from an entity for example. Also that mutltiple reactive systems can react to the same "event". Or to be more precise, to the same group of collected entities.
See both FallSystem and FillSystem reacting both to the same event and thus reducing the need for boilerplate code and components.
I get why EnTT has been architected this way, but maybe there is some kind of (opt-in) middle ground to alleviate the need for boiler plate in case one needs this?
As a new user of EnTT I accidentally used assign instead of attach when working with tags and was wondering if a clearly separate API would help reduce these kind of (user) errors. Something like addTag, removeTag, hasTag, replaceTag ...
What's especially confusing for me here is, that one mostly uses the regular API for Tags, but can't do that for replacing a tag(-component):
auto ScoreEntity = Registry.attachee<ScoreComponent>();
auto Score = Registry.get<ScoreComponent>();
Registry.attach<ScoreComponent>(ScoreEntity, Score.Value + 1);
Maybe it's ok to update components without ever telling EnTT about it?
When looking through registry.hpp there's lots of places describing undefined behaviour, e.g. when adding a component to an entity twice. The latter is clearly a user error (and sometimes hard to track), but one that I expect to happen from time to time. Wouldn't it make more sense to throw/assert by default there and not just in debug mode? So that users of EnTT have strict behaviour as a default and can opt-out if the need arises. Though to be honest I can't think of a reason why one would want a chance for undefined behaviour in the first place?
The last two things are related to Unreal Engine 4 in combination with EnTT:
UE4 defines a macro named ensure and uses it in various places deep in the engine code, making it practically impossible to not have it included in some place of the user code (resulting in compiler errors). Do you see any chance of renaming ensure to something else? :)
Also when exporting the project, the UE4 build tool fails, because of the noexcept specifier and it's (UE4) policy of disabled exception handling. That behaviour can hackishly be disabled for exported builds - but I think it's in the same category as the undefined behaviour by default described above. Would it be possible to change EnTT so that the excpetion behaviour could be selected via a macro for example?
Thanks for EnTT and looking forward to your feedback :)
I also made my own implementation of an UE4 integration: https://github.com/vblanco20-1/ECS_SpaceBattle
Used it to create this space battle: https://gfycat.com/EdibleCourteousHorse
On the "noexcept" issue, the easiest way would be to just have a #define NOEXCEPT noexcept or similar macro, to have it or remove it when you want. Will probably do that myself.
@vblanco20-1, @mhaemmerle, don't you want, guys, to work on ECS plug-in/module for UE4 based on EnTT? With GUI and stuff.
By the way, have anyone successfully compiled custom GameLoop [maybe, at least w/o EnTT]?
@UnoYakshi The implementation im doing is not going to be a public plugin, im going to use it for my new VR game where i need super-stable framerates and high performance on PSVR wich has a shit cpu. Some of the stuff would be NDA-d. But you can see how i implemented integration beetween UE4 actors and ECS entities in the Space Battle project.
@vblanco20-1, yeah, already did that::). No, I'm not talking about this exact implementation, but rather about a separate project.
How are you dealing with the UI/editor integration @UnoYakshi ? If you see in my battle example, im using "Wrapper" components that just register themselves with the ECS.
@vblanco20-1, alas, for now, I didn't understand the exact pipeline for extending EdEditor/Editor due to the lack of documentation. However, I've found Rama's example but haven't enough time to investigate it properly.
There are 3 main types of plug-in/modules (don't remember how it's called correctly, but UE4 has both, plug-ins and modules): Run-time, Development, Editor.
The GUI extension considers base code-generation functionality like AddSystem(FName/FString Name), TurnOffSystem(SomeSystemPtr) (actually drop-down/tree/search field in GUI), AddComponent(...) etc.
Didn't success much with EnTT compilation (after renaming EnTT's ensure) and rewriting UE4's GameLoop.
@mhaemmerle I'm out of home until Wednesday. I'll reply to all your questions as soon as I'm back to home. Be patient and don't worry. ;-)
Here I am, sorry if I'm late!!
I've ported the EntitasCSharp/Unity3D MatchOne example to EnTT/Unreal Engine 4, which can be found here under MatchOneEntt. It's a really small example and I would love to get feedback whether the places using EnTT could be improved in an idiomatic way.
A few hints.
In general, I don't like to put the registry in a locator. Services are something different, sort of tools to get rid of a bunch of ifs all around the codebase.
As an example, consider an audio service. You can implement two classes, WorkingAudio and NullAudio that both derive from AudioService, then set the right one as a service. Whenever the user turn off the audio, you can just switch the concrete class within the locator and that's all. The whole software will still work as if the audio is up and running, but for the fact that the null object does nothing. It helps a lot in getting clean your codebase.
On the other side, the registry isn't properly a _service_ as described above. It seems that you want something like a singleton more than a service, am I wrong?
You can also do something different with tags. As an example, consider this snippet from your code:
auto ScoreEntity = Registry.attachee<ScoreComponent>();
auto Score = Registry.get<ScoreComponent>();
Registry.attach<ScoreComponent>(ScoreEntity, Score.Value + 1);
Set aside the fact that I'm not sure the snippet above works as intended, you can write it as it follows:
Registry.get<ScoreComponent>() = ScoreComponent{ Score.Value + 1 };
Or something along this line at least. Keep in mind that the get member functions always return references that you can freely modify. This is valid also in case of components.
The rest of your code looks fine. You used a lot persistent views. Note that they increase memory usage to give you better performance, that's all.
In general it is quite pleasant to work with EnTT, though I do miss some features coming from Entitas-CSharp. Things like reactive systems that get triggered when a component gets added/removed from an entity for example. Also that mutltiple reactive systems can react to the same "event". Or to be more precise, to the same group of collected entities.
[...]
I get why EnTT has been architected this way, but maybe there is some kind of (opt-in) middle ground to alleviate the need for boiler plate in case one needs this?
Events have a great problem: if you add them for all the components and you are interested only in a couple of components, you waste a lot of cpu cycles for nothing.
To be honest, I'm trying to improve the way persistent views work because they suffer more or less from the same problem. Moreover, I'm trying to implement support for events for you're not the first one that is asking them. The sole difference with the other libraries will be that users must explicitly enable or disable signalling for components, so that you don't have performance hits for something you don't use. This is the idea behind EnTT after all: _pay only for what you use_. ;-)
As a new user of EnTT I accidentally used assign instead of attach when working with tags and was wondering if a clearly separate API would help reduce these kind of (user) errors. Something like addTag, removeTag, hasTag, replaceTag ...
The funny part is that having the same names for those functions helps me a lot because I don't have to memorize different names!! :-D
As a rule of thumb: if you work with components then the member functions want an entity as a first argument; if you work with tags, entities aren't required but for the attach member function (that is the only one that has a different name, actually - attach vs assign).
Maybe it's ok to update components without ever telling EnTT about it?
Yep, definitely:
registry.get<Component>(entity) = Component{param1, param2, ..., paramN};
It works just fine as long as your component has copy/move constructors/assignment operators whether required.
When looking through registry.hpp there's lots of places describing undefined behaviour, e.g. when adding a component to an entity twice. The latter is clearly a user error (and sometimes hard to track), but one that I expect to happen from time to time. Wouldn't it make more sense to throw/assert by default there and not just in debug mode? So that users of EnTT have strict behaviour as a default and can opt-out if the need arises. Though to be honest I can't think of a reason why one would want a chance for undefined behaviour in the first place?
No, this wouldn't turn to be a good idea. Consider this assert from the registry:
assert(valid(entity));
You don't want it's called each time you do something with the registry. Behind the scenes it does _something_ and involves branches, you want to avoid it in release. Trust me.
EnTT helps you with assertions in debug mode. That's all. If you are convinced on doing something wrong, it can't fix your production code. If you buy an auto, do you get also an assistant that avoids you crash in a wall? No, right? :-)
The last two things are related to Unreal Engine 4 in combination with EnTT:
UE4 defines a macro named ensure and uses it in various places deep in the engine code, making it practically impossible to not have it included in some place of the user code (resulting in compiler errors). Do you see any chance of renaming ensure to something else? :)
Also when exporting the project, the UE4 build tool fails, because of the noexcept specifier and it's (UE4) policy of disabled exception handling. That behaviour can hackishly be disabled for exported builds - but I think it's in the same category as the undefined behaviour by default described above. Would it be possible to change EnTT so that the excpetion behaviour could be selected via a macro for example?
Put them in the TODO list. Is assure fine for UE4? Any other suggestion?
Thanks for EnTT and looking forward to your feedback :)
You're welcome!! Do not forget to star the project so as to spread it more and more!! Thank you.
Thanks for your feedback!
This actually work(ed) as intended :) - even though it's obviously way too verbose:
auto ScoreEntity = Registry.attachee<ScoreComponent>();
auto Score = Registry.get<ScoreComponent>();
Registry.attach<ScoreComponent>(ScoreEntity, Score.Value + 1);
Though as per your suggestion I've replaced it with this here:
auto &Score = Registry.get<ScoreComponent>();
Score.Value = Score.Value + 1;
But to be honest I'd love to use this here (which currently doesn't work):
Registry.replace<ScoreComponent>(Registry.get<ScoreComponent>().Value + 1);
I don't know whether the replace member function currently not working with tags is intended or an oversight, but IMHO it infringes on the principle of least surprise when the other member functions work on either the registry or an entity depending on first argument.
This gets more interesting when we consider this example:
struct ScoreComponent { int Value; };
...
Registry.assign<ScoreComponent>(0);
Registry.replace<ScoreComponent>(1);
The replace member function expects an entity as first argument as per following declaration:
template<typename Component, typename... Args>
Component & replace(entity_type entity, Args &&... args) {
So the example will compile, but fail during execution :(
Which leaves with the following options:
replace function for tags (as compared to get, has, ..)replace for tags, but don't allow int types as struct members (lol)entity_typereplaceTag, hasTag, ...PS: I've starred the project a long, long time ago :)
Interesting overview, I've never had the need to _replace_ a tag actually but I completely understand what you say.
I'm putting it in the TODO list. I'll try to work around the _limitation_ somehow if possible.
Side question: what do you expect from this issue?
I mean, you opened #63 and that's fine for me. I think I'll do it as soon as v2.5.0 is released actually.
However I'm not sure if I can do something else for this one, set aside the fact that I added a few notes for _future works_ to the TODO list.
Do you want to keep it open for some reasons or can we close it?
accept discrepancy in replace function for tags (as compared to get, has, ..)
replace suffers exactly from the same problem of assign. That's why there exists attach.
In order to avoid breaking backwards compatibility, probably the best choice is to define a new function (change? exchange? substitute?) and to accept the (actually already existent) discrepancy for tags.
What about?
Side question: what do you expect from this issue?
The intention was to report from my findings of using EnTT in combination with UE4. Now that we've found some things that could be accomodated in EnTT, I think it makes sense to split those off into new issues that can be tracked, discussed upon and participants automatically notified on changes.
If you're already working on most of the things and prefer to talk in this issue, then I'am also fine with that.
replace suffers exactly from the same problem of assign. That's why there exists attach.
In order to avoid breaking backwards compatibility, probably the best choice is to define a new function (change? exchange? substitute?) and to accept the (actually already existent) discrepancy for tags.What about?
One of the reasons I'am such a big proponent of different names for all of the tag-related methods :) You could separate tag and regular API as a breaking change towards a version 3.0?
Though in response to the question itself, what about set (as used in Entitas here)?
I don't know how you will implement events on the components (add/replace/remove), but I think there's a fair chance that you will trigger them in the respective methods - which gives us another reason to also handle updating flags via a specific API method compared to just changing the struct itself.
If you're already working on most of the things and prefer to talk in this issue, then I'am also fine with that.
Good. I'll open new issues to keep track of changes if required and close this one then.
Thank you very much for your comments and your answers. Users are the best thing that happened to EnTT and they help driving the development of the framework.
One of the reasons I'am such a big proponent of different names for all of the tag-related methods :) You could separate tag and regular API as a breaking change towards a version 3.0?
Step by step then. We can add a new member function for the 2.x family, then replace all of them at once in 3.x and accept breaking changes in that case.
I'd like to work on custom allocators and make them the _core feature_ of the next major release. I think it will take at least a while before a v3.0.0 because of this.
Be patient. I'm developing EnTT in my free time because of no sponsors and few donations so far. I can't work on it full-time unfortunately, even though I admit it would be great!! ;-)
I don't know how you will implement events on the components (add/replace/remove), but I think there's a fair chance that you will trigger them in the respective methods [...]
Actually no. I've still to explore the idea I've in mind, but it doesn't require to modify assign and the others.
I won't add events unless a _pay for what you use_ mechanism is possible. To implement it I need to do some changes to the internal data structures of the registry and I'll use probably the SigH class (well, it's already there, why not?).
My pleasure and thanks for being open to changes and feature requests :)
@mhaemmerle
Added set and move member functions to the registry. They can be used to replace a tag and to transfer the ownership of an already existent tag to another entity.
You can test them on the experimental branch.
I'm closing the issue because I've already kept track of all the other requests and I'll work on them sooner or later.
I'll open new issues if required to discuss each change separately. Take a look at the TODO file for more details.
@mhaemmerle
Side note.
Another way to disambiguate could be the one already in use in the standard library.
Something along this line:
// Components
registry.assign<Component>(entity, params...);
// Tags, tag dispatching does the work
registry.assign<Tag>(unique_tag{}, entity, params...);
What do you think about?
Interesting, though in my humble oppinion it still shows a weakness in an otherwise excellent API.
@mhaemmerle
Another way to implement it could be through CRTP idiom.
As an example, if all single instance components inherit from a class like this:
template<typename Component>
struct SingleInstanceComponent {
// ...
};
struct MyTag: SingleInstanceComponent<MyTag> {
// ...
};
We can easily intercept their types through sfinae based techniques and thus all the member functions can have exactly the same name (assign, replace, and so on).
I'm not a fan of forcing an inheritance step anyway, but it could work actually.
I also made my own implementation of an UE4 integration: https://github.com/vblanco20-1/ECS_SpaceBattle
@vblanco20-1 Very cool demo, thanks for making it public!
While working with EnTT in Unreal, did you find a way to make EnTT's debug checks work? They are based on the standard assert mechanism, which requires the debug CRT. I tried turning on bDebugBuildsActuallyUseDebugCRT in my build config, but that didn't seem to be enough.
For example, an EnTT view will assert that it contains the requested component, but it's only a debug check.
@bbi-yggy what do you mean with _make debug checks work_? You can also disable them all in debug to speed up things if you prefer. There is a FAQ that describes how to do that (but you need at least EnTT v3 for that).
@bbi-yggy what do you mean with _make debug checks work_?
I'm getting a crash I don't understand, and I hoped that the asserts in EnTT would help me figure out what's wrong, but there's no sign of the asserts being triggered.
I don't see any sign of asserts in the Unreal output log or when running in the debugger.
I then tried directly calling assert(0); to see what an assert should look like, but that didn't trigger either. Turns out NDEBUG is always defined, which disables the asserts. I got them back like this:
#undef NDEBUG
#include <entt/entity/registry.hpp>
In the end my problem was due to a silent conversion of true to entity_type - I changed a method that originally returned a bool to return an entity instead, but neglected to modify the return statement. No compiler error or warning, but now my entity creation method always returned true - i.e. entity 1. Hilarity ensued.
@bbi-yggy
Identifiers are actual types on master and no longer plain integers, so as to avoid this kind of problems. As you can guess from this change, you aren't the only one that has run into the issue. :wink:
Aha, good to know! I'm currently on an older version of EnTT (from September 2018) that a colleague of mine had been experimenting in Unreal with a few months ago. I used his project as a starting point. Unreal supports C++17 as of 4.22, so I was planning to investigate upgrading to EnTT 3.0 but I haven't tried yet.
On that note, is there an easy way to see what version of EnTT I have from the source code?
@bbi-yggy I'm not sure it was already there at the time, but now there is a file named version.h under src/entt/config. It exports some variables that contain also the major, minor and patch numbers. Otherwise you can look at the CMakeLists.txt in the root directory, it contains the version for sure.
There's no version.h and no CMakeLists.txt either - I copied from my colleague's project, and looks like he only used src/entt. But never mind. I know it's old and I know I need to upgrade.
BTW, when integrating entt, do you recommend using the latest master?
Or a tagged release (like v3.0.0)?
@bbi-yggy latest master is v3.1 but for a feature I'm still working on, so I would say - _use it_. If you prefer a freezed version so as not to deal with changes, tags are the way to go instead.