|
Boost : |
Subject: Re: [boost] Boost.Mixin library preliminary reviews
From: Borislav Stanimirov (b.stanimirov_at_[hidden])
Date: 2014-02-11 15:38:33
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_at_[hidden]>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
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk