DynaMix

A New Take on Polymorphism in C++

What is DynaMix?

 

  • Compose and modify objects at run time
  • hard to explain in 5 minutes

A hostile AI component

struct hostile_ai
{
    void act()
    {
        auto guys = find_good_guys();

        move_to(guys);

        attack(guys);
    }
    ...
};

A player controlled component

struct player_controlled
{
    void act()
    {
        auto key = read_keyboad();

        if(key == left)
            move_left();
    }
    ...
};

A flying creature component

struct flying_creature
{
    void move_to(target t)
    {
        flap_wings();

        my_pos += norm(t.pos() - my_pos) * my_speed;
    }
    ...
};

Composing, the old-fashioned way

  • multiple inheritance (MI)
    • how do components communicate?
  • using CRTP with compile-time mixins
    • ​everything is in headers
    • array of objects - virtual inheritance of base class
      • ​vector<object*> == vector<void*>
  • MI + ​​​virtual base class + no CRTP
    • ​all possible methods exist as virtual in the base
      • combinatorial explosion of manually added types
      • base class is a coupling focal point
      • runtime mutation not possible

Interface to component pattern

struct component { object* self; };
struct Icontrol  { virtual void act() = 0; }
struct Imobility { virtual void move_to(target) = 0; }

struct object {
    Icontrol* control;
    Imobility* mobility;
};

struct hostile_ai : public Icontrol, public component {
    void act() override { self->mobility->move_to(good_guy); }
};

object dragon; // time to compose
dragon.mobility = new flying_creature;
dragon.control = new hostile_ai;
dragon.control->act();
dragon.control = new player_controlled; // mutating the control
  • every new interface has to be added to object
  • what if we want to implement only a part of an interface?

The DynaMix way

// compose
object dragon;
mutate(dragon)
    .add<hostile_ai>()
    .add<flying_creature>();

act(dragon); // calls act() from hostile_ai - no uniform call syntax :(

// mutate - change control from AI to player
mutate(dragon)
    .remove<hostile_ai>()
    .add<player_controlled>();

act(dragon); // the same call continues to work but does something else
struct hostile_ai {
    void act() {
        auto guys = find_good_guys();
        move_to(dm_this, guys); // dm_this == self - the object we are part of
        attack(guys);
    }
};

Overview

  • Building blocks
    • object
    • messages - global-function-like pieces of interface that an object might implement
    • mixins - component classes you have written that can implement messages
      • not to be confused with CRTP mixins
      • DynaMix == dynamic mixins
  • Usage
    • mutations - adding/removing mixins from objects
    • calling messages - like calling methods
      • object is the first parameter - no uniform call syntax :(

Message types

  • Unicast
    • only one mixin can implement a message
    • or multiple with different priorities
      • so messages can be shadowed/overridden
  • Multicast
    • any number of mixins can implement them
    • example:
      • serialization of all mixins in an object

Info about DynaMix

  • it enforces good OOP practices
    • composition over inheritance
    • loose coupling
    • separation of interface and implementation
  • perfect for:
    • when dealing with complex polymorphic objects
      • Games (especially RPGs)
      • CAD
      • GUI
    • plugins that enrich/customize objects

2017 CppCon - DynaMix lightning talk

By Viktor Kirilov

2017 CppCon - DynaMix lightning talk

A lightning talk about DynaMix for CppCon 2017

  • 2,385