
On 02/11/2014 06:54 PM, Klaim - Joël Lamotte wrote:
On Tue, Feb 11, 2014 at 7:22 AM, Borislav Stanimirov <b.stanimirov@abv.bg>wrote:
8. I'm not totally sure but I find the basic example maybe a bit more complext than it could be. That's only intuition though, I don't have a specific suggestion. Yet.
Are you talking about the one in Basic Usage here: http://ibob.github.io/boost.mixin/boost_mixin/basic.html
Yes.
Well, it shows the most basic features. Creating messages and mixins, and creating and mutating objects with them. I don't think it can get any simpler than this. If you do have a suggestion, I'd love to hear it :)
10. When dispatching a message to a sequence of objects, depending on the
object's mixins, it will or not do something. I would like to see a comparison of performance between messaging objects which have at least one mixin reacting to the message VS objects which have no mixin reacting to the message (but do have mixins). Basically I'm wondering if messaging cost is lower when there is no actual message handler in the mixins of an object. If not, this limits messaging to events processing (which is fine), but makes it almost impractical for mixin state updating (which is the what I want to do in [9]).
Calling a message for an object that doesn't implement it, is a runtime error (like calling a method for plain C++ class that doesn't implement it is a compile time error). If you want something like default message behavior whether it's nothing or something, you'll have to add mixins that implement it (with nothing or something) to your objects.
So, If I have a set of objects which I don't want to know the associated components, but I do want to send a multicast message that will be processed only by some of the components, currently I can't do this because I would get an error if one of these objects have a component which don't have a handler for that message? Isn't that limitting a lot the usage of mixins/components?
Oh. I must have misunderstood you, then. If the message is a multicast and at least one of the mixins in the object implements it, it will be executed. When the call table for a multicast is created, the library allocates a buffer for all the message handlers in an object. So if an object has ten mixins and one of them handles a multicast message, the actual message function will end up "looping" through an array of one element. If two mixins handle it, the loop will be passed through twice, and so on. This is an exact paste from the generated calling function: for(const call_table_entry* _b_m_iter = _b_m_begin; _b_m_iter!=_b_m_end; ++_b_m_iter) { //... // the call follows _b_m_func(_b_m_mixin_data, args ); } However if no mixins in the object implement the multicast message, a runtime error will be triggered.
13. I feel like your game example in Tutorials is a bit flawed. For example, you use mutation to change the behaviour of the ennemy, but in the next section you explain that mutation is a slow process. Basically, that's not convincing, no game dev would do it like that I believe. However, it's only the example that is not convincing, not the principle.
When I say "slow" I mean that it's not suitable to do for all of your objects every frame. Mutations typically lead to allocations and deallocations and that's the slowest part of them.
Which is why I'm pointing to the fact that game devs would totally avoid such practice, which is why I find that example unconvincing. I'm sure another case could be found that would be more convincing.
The example's flow is something like
1. "stun" 2. ... many frames with no mutation 3. "remove stun"
If there is allocation and deallocation, it's rarely affordable for a game dev, at least in action games. To bo short, it's known to be bad practice in the field. Which is why I'm pointing that. (even if it's not an actual library problem, more a documentation problem)
If even a single allocation is a problem, the only solution is to use custom allocators for every mixin that may end up being mutated in or out during the main loop. I'll add a warning in the tutorial example that the mutation would lead to allocations/deallocations if no special precautions are made.
I should (and will) add a more detailed explanation for the mutation costs. But a couple of mutations should be able to cause a significant framerate spike (especially if you use custom allocators for some mixins you add and remove often)
Would it be possible to avoid allocations? It looks liike this system have similar properties to Boost.Statecharts which allocate/deallocate states and use constructors and destructors as entry and exit points. I believe that Boost.MSM is actually a better (more efficient) way to do it as the whole thing is pre-allocated. I don't know how you could follow a model closer to Boost.MSM but the allocatoin/deallocatoin for a mutation seems like it could be avoided under some conditions?
With no information about object count and size, allocations cannot be avoided. Theoretically something like `std::vector::reserve` could be implemented, but it would either need to be based on bytes (and not on mixin or object count), or would need to have complicated parameter schemes which list possible mixin configurations. The whole idea behind Boost.Mixin is to provide an alternative to the classic C++ polymorphism. Most of your examples don't need polymorphism but static types. While the library can help you a bit in such cases, its not designed to be used when polymorphism is not needed. In cases like the ones you cite, where cache locality, strongly typed references to concrete non-polymorphic classes are _crucial_, directly using the library -- objects and messages -- is simply not a good idea. The custom allocators are the only thing (at least in the foreseeable future) that can help you in similar cases. Still, since no polymorphism is needed in such a case, perhaps simply having a mixin like this: class rendering_mixin { // this member is used by the rendering system // it allocates it deallocates it and it threads // through a list of these every frame // while getting it from its object is indeed a O(1) // operation, it leads to cache misses, so use with caution rendering_implementation* _rendering; void set_rendering_implementation(rendering_implementation*); // called on reallocation void update_rendering_implementation(rendering_implementation*); } ... could be a better solution If you do need polymorphism, you're bound to have cache misses, and Boost.Mixin is an appropriate choice.
15. In the Appendix you explain what an ECS is, but it's uncomplete and don't explain all the benefits.
Noted. I will try to expand it.
There are different types of ECS:
a) component types inherit from a base type, components instances are contained/owned by the entity instance; b) same as a) but components are actually gathered in arrays by types, entities instances only have references (pointers or something else) to component instances; c) entities are just ids, components have ids, so an entity is defined by all the components having the same id;
<snip>
I'm not sure what Boost.Mixin actually implements here, but if it's neither b) nor c), I would consider it unpratical in most action games.
As a side note: If there is a way to achieve processing an array of components as one batch, then I would like the container of components to _not_ be global (maybe optionally), so that if I use it, I can separate the processing into several "world sections" that can be processed separately.
As I mentioned before. It could be something like b) but only for some mixins, that you've chosen, by adding custom allocators.
Without custom allocators it is indeed neither b) nor c)
Ok that was not clear: you mean that the user have to provide allocators for mixin types to allow the behaviours of b) and c) and concurrent update of mixin instances? If yes, could you provide at least an example of upating mixing instances in a pool or something, as documentation? I think helping the user setting up easily such system would help devs getting interested in this library. Just saying that you can provide allocators suggests that you'll have to do the plumbery work to make the library work as I think most people would expect (with mixins stored in something like vectors or pools per type). Could you provide helpers for that?
Yes, I will provide examples of allocators where the mixins to be allocated are kept in a pool. Still to update the mixin pointer within an object (something you might need on reallocation) is a feature I'm still not sure if I'll implement. So if I don't, such an allocator would need to have multiple pools (like a free list), or allocate an appropriate (huge) amount of address space, and manually manage its pages. If you're expecting to have many "holes" in you memory, a solution like the one shown above (placeholder mixin) should be implemented. -- Borislav