Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Aurora  (Read 29784 times)

0 Members and 1 Guest are viewing this topic.

Lolilolight

  • Hero Member
  • *****
  • Posts: 1232
    • View Profile
Re: Aurora
« Reply #30 on: January 15, 2014, 04:51:13 pm »
Ok so I'll use pointers.

Erf, the problem is that I don't know the types and the number of types in advance so I need a variadic template. (My class can accepts any kind of functions with a variable argument list, and have a tuple to store the values of the arguments of the function)

And the signal use this object to store any kind of signal connected to any kind of slot and call it later but the problem is I've to pass the values of the arguments when I want to connect the slot at the signal, beacuse, if the template list is empty it crashes :
template <typename S, typename... A> static void connect (const std::string &key, S slot, A... args) {
         Signal *sign = new SignalWithoutReturn<S, A...> (slot, args...);
         std::map<std::string, Signal*>::iterator it = signals.find(key);
         if (it != signals.end())
            throw Erreur (20, "This id is already used!", 0);
         else
            signals.insert(std::make_pair(key, sign));
     }
 

Normal because if I don't fill the args variable in the connect function with values, it create a signal to a function with no arguments types, so, when I want to call it, it crash. ^^
I thought I could send a message at compilation time with your library but, it doesn't seems that we could.
The second thing I want to do is to pass placeholders at the signal who can be remplaced in the tuple by the real values of the arguments when I call the signal. Exactly like in this tutorial but with tuple, not with lists : http://accu.org/index.php/journals/1397

The advantage of this system is that I've one type for any kind of signals (type erasure), not like std::bind where the type is unspecified so I cannot store an object with the std::bind type.
I don't really like the type auto and I don't really like the std::tuple class too. (tuple are difficult to manipulate and the auto type don't allow us to store objets so it's utility is very limited and I don't find it very well in a typed language like c++.)
The recursivity of variadic templates give me an headache too because we have to use the template specialisation and structures to do this instead of functions.
« Last Edit: January 15, 2014, 05:04:02 pm by Lolilolight »

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
[Aurora] Preprocessor metaprograms
« Reply #31 on: January 19, 2014, 11:24:12 am »
Preprocessor metaprograms

Some of you might wonder how named tuples are implemented in Aurora. How can it be that the macro
AURORA_NAMED_TUPLE(MyTuple, ((int, i), (float, f)))
generates the following code?
struct MyTuple
{
    MyTuple(int i, float f) : i(i), f(f) {}

    int i;
    float f;
}

The answer leads to a dark corner of C and C++: Preprocessor metaprogramming. In general, macros have a bad reputation, and mostly they can be avoided by using other abstraction techniques such as templates. However, legitimate use cases remain; templates and even template metaprogramming don't cover every situation. For example, a previous post of mine shows the drawbacks of std::tuple, namely its lack of expressiveness.

Most people are not aware that the C preprocessor is not only useful for simple text processing, but it has the capabilities of code generation -- before compile time, and within the language itself. As such, it is very powerful in specific situations where the only alternative would be manual and error-prone code repetition. These situations are rare, but when you encounter them, the preprocessor can prove invaluable.

Aurora provides a minimalistic preprocessor metaprogramming toolbox. While inspired from Boost.Preprocessor, some functionality is tweaked for specific needs, and some parts are simplified for the user (e.g. by the use of variadic macros). To show you how easy and powerful it is, let's define an enum which allows conversion to strings.
#include <Aurora/Meta/Preprocessor.hpp>

#define ENUMERATOR(value, index)    value,
#define TO_STRING(value, index)     case value: return AURORA_PP_STRINGIZE(value);

#define SMART_ENUM(Enum, sequence)              \
enum Enum                                       \
{                                               \
    AURORA_PP_FOREACH(ENUMERATOR, sequence)     \
};                                              \
                                                \
const char* toString(Enum e)                    \
{                                               \
    switch (e)                                  \
    {                                           \
        AURORA_PP_FOREACH(TO_STRING, sequence)  \
    }                                           \
}                                               \

Once we have defined the preprocessor metafunction SMART_ENUM, we can use it to generate code.
SMART_ENUM(Color, (Red, Blue, Green))
expands to:
enum Color
{
    Red,
    Blue,
    Green,
};

const char* toString(Color e)
{
    switch (e)
    {
        case Red: return "Red";
        case Blue: return "Blue";
        case Green: return "Green";
    }
}

And so, we can easily convert enumerators to strings.
int main()
{
    Color c = Green;
    const char* s = toString(c); // s is "Green"
}

Since the preprocessor doesn't allow iteration or recursion, metaprogramming helpers are implemented by means of manual repetition, which limits the number of processed elements. At the moment, sequences may not be longer than 5 elements, but this can be extended.

What do you think about named tuples and preprocessor metaprograms? Even if you don't need them immediately, can you imagine their utility? :)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Lolilolight

  • Hero Member
  • *****
  • Posts: 1232
    • View Profile
Re: Aurora
« Reply #32 on: January 19, 2014, 02:45:43 pm »
Hum..., it depends on what the c++'ll reserve to us for later.

Personnaly I never used macro because of their reputation but, in the future why not, if the next c++ version doesn't offer us a more efficient way to manage tuples and exceptionnaly tuples with variadic templates to pass them to a function.

It'll be usefull to use a macro.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Aurora
« Reply #33 on: January 20, 2014, 10:50:22 pm »
Other opinions on named tuples, dynamic dispatchers or preprocessor metaprograms? Or Aurora in general?

I'm totally aware that many of Aurora's features are very specific, and a lot of people don't need them. The idea is that when you ever need them, you'll be glad that you don't have to write, test, debug a lot of boring code -- especially for more complex things like dynamic dispatchers. I can imagine a lot of you already came in a situation where a similar technique has been necessary (see collision example), how did you solve it then?

A notable exception to the rare use cases is aurora::CopiedPtr; I'm using it everywhere, and have found it extremely helpful so far. How do you copy polymorphic objects in case you need to?

In general, Aurora's philosophy is to be very lightweight, modular and easily integrable. You don't have to link anything and can directly start, and you can use only the parts you need. Other libraries with a similar application field (Boost) are the exact opposite. I'm really open for input regarding the library, especially if you don't use Aurora. Does the library look useful, but you have not had a situation where it would have fit; have you simply not known it well enough; or do you consider it a complete waste of time? :)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

MadMartin

  • Jr. Member
  • **
  • Posts: 74
    • View Profile
Re: Aurora
« Reply #34 on: January 21, 2014, 09:05:31 am »
As almost every contribution you make, Nexus, this is C++-wise genius  ;)

My "problem" is that I haven't found a situation where Aurora could be helpful. For me, this is because you introduce very sophisticated programming techniques.

If you have the time and willingness, I'd absolutely love to see a blog of yours!
Although I consider myself an experienced user of C++, I always read your posts with great interest. For many of the features of modern C++ (C++11, RAII, Smart Pointers, etc.), your posts gave me new insight and/or more reasons to use them.
So, a blog with one or two nice articles about advanced C++ per month, written by you, would be something I even would pay for :)


But a simple set of nice and illustrating use cases for Aurora would be sufficient, to say the least! (Suggestion: Make them a little bit more elaborate than the examples on your page)

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Aurora
« Reply #35 on: January 21, 2014, 04:54:44 pm »
Thanks for the nice feedback! :)

Yes, bigger examples of Aurora's use cases might indeed be an idea. I kept them rather simple because I feared otherwise it would confuse people too much, and they would quickly lose the overview. But for example, are the code snippets in this thread too short? Or not "real-world" enough? For example, the codes about collision dispatchers and named tuples originate from an actual project of mine.

Yesterday, I also updated the Aurora project page and added an explanation about the backgrounds and some small example codes. I guess you have already seen it.

Concerning the blog, I already thought about that, too, since I'm writing (and repeating) a lot of such topics in C++ forums. I probably couldn't guarantee regular updates, so it would rather be a collection of articles on my homepage. Thanks for the interest, and if there are other people with opinions on that topic, don't hesitate to state them!
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Lolilolight

  • Hero Member
  • *****
  • Posts: 1232
    • View Profile
Re: Aurora
« Reply #36 on: January 23, 2014, 12:57:53 pm »
Yes your code shows us high level programing techniques (and gives us a lot of inspiration), but it's too specific for your case, or, I need to have something who's more general.
So I don't have to use your system for the moment, but later maybe it'll be usefull.

For the moment I don't manage collisions with a dispatcher like you, I manage collisions like this :
I've a base class and derivated classes who are bounding areas. (or bounding volumes)
And I use the polymorphism to redefine the method intersects for any specific bounding areas (or bounding volumes) and to attach any kind of bounding areas or bounding volumes to an Entity. (And them I can determine if the two collisions areas or volumes of the entities intersects or not)
I do also some optimisation by storing entities into a grid structure, to avoid to do the collision test with all entities of the scene.

I've found an interesting technique to solve collisions for any king of bounding areas or bounding volumes. (The SAT theorem) witch consist of projections of normals vectors of the bounding areas to an axis. (It work for 2D and 3D shapes)
You can find easy some informations for physics like the minimal translation vector.

The link to the article : http://www.codezealot.org/archives/55

Later I'll cut the boundings areas or volumes into smallest bounding areas to have a hierarchy.
I'll have a more perfect collision result so.

But there are many techniques used fo collision detection (pixel perfect, bounding volumes/areas, etc...)
I like the technique presented in this following links because it works in very general cases. (2D and 3D environnements)
This is the technique who's used in many physics engines.

Lo-X

  • Hero Member
  • *****
  • Posts: 618
    • View Profile
    • My personal website, with CV, portfolio and projects
Re: Aurora
« Reply #37 on: January 23, 2014, 01:32:22 pm »
Yes your code shows us high level programing techniques (and gives us a lot of inspiration), but it's too specific for your case, or, I need to have something who's more general.
So I don't have to use your system for the moment, but later maybe it'll be usefull.

Sorry to disagree but no it's not (for the tuple part, dispatcher part is fine IMHO). Indeed I know why to use your tuple, I think I see the point of using yours instead of std::stuple, but honnestely I don't think I have a practical case right now.

For the moment I don't manage collisions with a dispatcher like you, I manage collisions like this :
I've a base class and derivated classes who are bounding areas. (or bounding volumes)
And I use the polymorphism to redefine the method intersects for any specific bounding areas (or bounding volumes) and to [... cut the rest]

I don't want to be bad but I really do not care about the way you implement your collision detection in a thread that do not concern it. That was just a example of how to use the dispatcher. This is not a Box2d or <insert physics lib here> topic.
You still can explain us how you do your stuff on your blog or start a discussion in your threads IF it worth a thread on SFML forums.

Lolilolight

  • Hero Member
  • *****
  • Posts: 1232
    • View Profile
Re: Aurora
« Reply #38 on: January 23, 2014, 02:42:30 pm »
I've just answered to his question.

Quote
I can imagine a lot of you already came in a situation where a similar technique has been necessary (see collision example), how did you solve it then?

And talked about how I solved the collisions.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
[Aurora] Ranges II
« Reply #39 on: February 21, 2014, 05:51:59 pm »
Ranges II

I have rewritten the whole Range module and changed the design fundamentally. While the old ranges offered iteration in a rather specific way:
aurora::Range<...> range = ...;
for (; !range.empty(); range.popFront())
        std::cout << range.front() << " ";
the new ranges provide iterators, in order to fit better into existing designs such as the range-based for loop, the STL or Boost.Range:
for (auto itr = range.begin(); itr != range.end(); ++itr)
    std::cout << *itr << " ";

There are two main classes in Aurora: ranges and iterators. T is the element's value type,  C denotes the iterator traversal category. Here is a link to the documentation.
template <typename T, typename C>
class Range;

template <typename T, typename C>
class RangeIterator;

Often, you won't need to specify the types explicitly, as there are maker functions you can combine with C++11 type erasure. To show what Aurora's ranges are capable of, here is a short example:
int main()
{
        // Create 3 containers with different iterator categories
        std::vector<int> v       = { 401, 402, 403 }; // random access
        std::list<int> l         = { 501, 502 };      // bidirectional
        std::forward_list<int> f = { 601 };           // forward

        // Make a range over all three containers, change each element
        for (int& i : aurora::chain(v, l, f))
                --i;

        // Output each element, using different chain order
        for (int i : aurora::chain(f, l, v))
                std::cout << i << " ";

        // Output: 600 500 501 400 401 402
}

There's still a lot of functionality missing, but the basic building blocks are there. There's also room for optimization, especially for iterators. The aurora::chain() function is already optimized excessively: it uses heavy metaprogramming to infer the appropriate range types for each sequence at compile time, and stores them in a std::tuple in a single object to avoid dynamic allocations.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

FRex

  • Hero Member
  • *****
  • Posts: 1848
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: Aurora
« Reply #40 on: February 26, 2014, 01:33:24 pm »
I'm interested in trying to use DoubleDispatcher but I don't understand a few things:
1. What is trampoline in traits doing, when would I need it and when would I even need own traits class?
2. What is the difference between key and id in the traits class?
3. Is it possible to not throw and not do anything if a function is not found?
4. What would happen if one or both pointers passed to call were nullptr?

Also, you're using new and delete in the example, that is very not like you so I'm reporting it as a mistake here and now. :P
Back to C++ gamedev with SFML in May 2023

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Aurora
« Reply #41 on: February 26, 2014, 01:58:35 pm »
1. What is trampoline in traits doing, when would I need it and when would I even need own traits class?
The trampolines allow you to wrap a registered function automatically. For example, when using polymorphic types, you may want to register a function
void MyFunction(Derived1& lhs, Derived2& rhs);
but the DoubleDispatcher<Base> stores
std::function<Base&, Base&>
objects. So the trampoline would downcast the arguments to match your function signature.

2. What is the difference between key and id in the traits class?
The ID is passed when registering a function with bind(), the Key is used by the map that stores the functions. Often, both types are the same, but not always -- for example, the default dispatcher uses aurora::Type<T> as ID but std::type_index as key. The reason is that the former still contains static type information (namely the T type), which is necessary to build the correct downcast.

Keep in mind that the Traits exists for advanced customization in order to provide maximum flexibility (for example, you can use an inheritance-based approach as well as a entity-component-based one). Often, it will be enough to inherit from aurora::DispatchTraits and just define the necessary functions. See also this post to get a better overview.

3. Is it possible to not throw and not do anything if a function is not found?
Not yet, but such a fallback is planned. The problem I'm facing is the return type of call() in such a case. If it's not void, what should be returned? I don't want to restrict the used type to be default-constructible. One approach might be a specific fallback function defined by the user.

4. What would happen if one or both pointers passed to call were nullptr?
It won't work. With the default dispatcher I guess there will be a std::bad_typeid exception, but I have not tried it :)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

FRex

  • Hero Member
  • *****
  • Posts: 1848
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: Aurora
« Reply #42 on: February 26, 2014, 03:05:29 pm »
Third one is a very bad thing because now it throws. Not having a function to call for certain pairs of interacting object is not really exceptional situation (IMO). :(  Maybe there should be an overload that takes reference to return type as out parameter? If I run into problem with these exceptions I'm modifying the code and adding that since it's template only class and that'd take like 10 lines.

About the first: is there a default trampoline? There seems to be because functions taking derived and base or two derived get passed in to dispatcher that has functions that call on two base references and it works.
Also, if I let dispatcher be symmetric and have 'non symmetric' callbacks what will happen?
void my_callback(Derived1 * left, Derived2 * right) { /* ... */ }
dispatcher.bind(/* ect. */, &my_callback);
Derived2 one;
Derived1 two;
dispatcher.call(one, two); //does my_callback get called with one as right and two as left or not?
 
I'm guessing yes judging from these two fragments (but I still rather have a confirmation :P):
        // When symmetric, (key1,key2) and (key2,key1) are the same -> sort so that we always have (key1,key2)
        if (mSymmetric && hashValue(key2) < hashValue(key1))
                return Key(key2, key1, true);
        else
                return Key(key1, key2, false);
 
        // Call function (swap-flag equal for stored entry and passed arguments means the order was the same; otherwise swap arguments)
        if (itr->first.swapped == key.swapped)
                return itr->second(arg1, arg2);
        else
                return itr->second(arg2, arg1);
 
Back to C++ gamedev with SFML in May 2023

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Aurora
« Reply #43 on: February 26, 2014, 10:20:01 pm »
Okay, I have implemented the fallback functions. You can pass the NoOp functor if you want to do nothing:
aurora::DoubleDispatcher<B, R, Traits> dispatcher;
dispatcher.fallback( aurora::NoOp<R, 2>() );

I have also improved the dispatcher's documentation, especially the parts that were unclear for you. The new documentation is online.

There is a default trampoline in aurora::RttiDispatchTraits (the default value for the Traits template parameter), as well as a identity trampoline in aurora::DispatchTraits (the base class for custom traits).

Yes, dispatchers will automatically figure out the correct order of arguments when symmetry is enabled. If necessary, the arguments are reordered during the call.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Aurora
« Reply #44 on: June 06, 2014, 11:08:12 am »
I changed the template parameters of the dynamic dispatchers. The user can specify the function type (the signature) as the first template argument, which is more intuitive and closer to std::function.
aurora::SingleDispatcher<void(Base&)> dispatcher;
dispatcher.bind(aurora::Type<Derived>(), [] (Derived&) { std::cout << "D"; });

Derived d;
Base& ref = d;
dispatcher.call(ref); // output: D

It is now also possible to pass additional arguments when invoking the function. Its type can be specified as an additional parameter in the signature.
aurora::SingleDispatcher<void(Base&, int)> dispatcher;
dispatcher.bind(aurora::Type<Derived>(), [] (Derived&, int i) { std::cout << "D" << i; });

Derived d;
Base& ref = d;
dispatcher.call(ref, 5); // output: D5
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

 

anything