Prikaz jedne poruke
Stara 10.7.2014, 21:15   #1
Belphegor
V.I.P. Programiranje
 
Član od: 29.8.2007.
Lokacija: Valjevo
Poruke: 1.349
Zahvalnice: 983
Zahvaljeno 371 puta na 280 poruka
Određen forumom [C++] Unity sistem komponenti

Posle duze pauze rek'o da se vratim malo programiranju, a vidim da je i nesto slabo aktivan ovaj deo foruma.

Imao sam priliku da probam malo Unity endzin, posto je postao veoma popularan pa rek'o da vidim i ja o cemu se radi.
Ukratko, mnogo mi se svideo njihov "component system" pa sam probao da implementiram nesto slicno sa C++-som.

Postavicu kod ovde pa cu ispod malo da izkomentarisem.
Kod:
#include <iostream>
#include <string>
#include <unordered_map>
#include <type_traits>
#include <memory>
#include <cassert>
#include <vector>
#include <algorithm>


template<typename T>
struct type { static void id() { } };

template<typename T>
std::size_t type_id() { return reinterpret_cast<std::size_t>(&type<T>::id); }

class Component
{
public:
    Component() { }
    virtual ~Component() {}
};

class Transform final : public Component
{
public:
    int x, y;
    Transform(int x, int y) : x(x), y(y) {}
};

class Renderable final : public Component
{
public:
    std::string data;
    Renderable(std::string d) : data(d) {}
};

class Other final : public Component
{
public:
};

typedef std::shared_ptr<Transform>  sp_Transform;
typedef std::shared_ptr<Renderable> sp_Renderable;

class RenderSystem
{
private:

    friend class SceneManager;
    typedef std::vector< std::weak_ptr<Renderable> > t_Renderable;
    t_Renderable vRend;

    void AddRenderable(sp_Renderable r)
    {
        vRend.push_back(r);
    }

public:

    void Update()
    {
        for (std::size_t i = 0; i < vRend.size(); ++i)
        {
            if (auto sp = vRend[i].lock()) // not expired
            {
                std::cout << sp->data << std::endl;
            }
            else // component removed, swap - pop_back
            {
                std::swap(vRend[i--], vRend.back());
                vRend.pop_back();
            }
        }
    }
};

class PhysicsSystem
{
private:

    friend class SceneManager;
    typedef std::vector< std::weak_ptr<Transform> > t_Transform;
    t_Transform vTransform;

    void AddTransform(sp_Transform t)
    {
        vTransform.push_back(t);
    }

public:

    void Update()
    {
        for (std::size_t i = 0; i < vTransform.size(); ++i)
        {
            if (auto sp = vTransform[i].lock()) // not expired
            {
                sp->x += 3;
                sp->y += 7;
                std::cout << sp->x << ", " << sp->y << std::endl;
            }
            else // component removed, swap - pop_back
            {
                std::swap(vTransform[i--], vTransform.back());
                vTransform.pop_back();
            }
        }
    }
};

class GameObject;
typedef std::shared_ptr<GameObject> sp_GameObject;

class SceneManager
{
private:

    std::unique_ptr<PhysicsSystem>  psys;
    std::unique_ptr<RenderSystem>   rsys;

public:

    SceneManager()
    {
        psys = std::make_unique<PhysicsSystem>();
        rsys = std::make_unique<RenderSystem>();
    }

    sp_GameObject CreateObject()
    {
        return std::make_shared<GameObject>(this);
    }

    template < typename T >
    void EnqueueComponent(T t)
    { } // do nothing

    void Update()
    {
        psys->Update();
        rsys->Update();
    }
};

template <> // specialization for Renderable
void SceneManager::EnqueueComponent<sp_Renderable>(sp_Renderable t)
{
    rsys->AddRenderable(t);
}

template <> // specialization for Transform
void SceneManager::EnqueueComponent<sp_Transform>(sp_Transform t)
{
    psys->AddTransform(t);
}

class GameObject
{
    SceneManager* smgr;
    std::unordered_map<std::size_t, std::shared_ptr<Component>> components;
public:

    GameObject(SceneManager* s) : smgr(s) {}

    template < typename C, typename... Args >
    void AddComponent(Args&&... args)
    {
        assert("Already have that component!" && components.count(type_id<C>()) == 0);
        auto c = std::make_shared<C>(std::forward<Args>(args)...);
        smgr->EnqueueComponent(c);
        components.insert({ type_id<C>(), c });
    }

    template < typename C >
    bool HaveComponent()
    {
        return components.count(type_id<C>()) != 0;
    }

    template <typename C>
    std::shared_ptr<C> GetComponent()
    {
        assert("No such component!" && HaveComponent<C>());
        return std::static_pointer_cast<C>(components[type_id<C>()]);
    }

    template <typename C>
    void RemoveComponent()
    {
        components.erase(type_id<C>());
    }
};

int main()
{
    auto s = std::make_unique<SceneManager>();

    auto o1 = s->CreateObject();
    o1->AddComponent<Transform>(5, 9);
    o1->AddComponent<Renderable>("Hello obj1!");

    auto o2 = s->CreateObject();
    o2->AddComponent<Transform>(11, 22);
    o2->AddComponent<Renderable>("Hello obj2!");
    
    //auto z = o1->GetComponent<Other>(); // error 

    //auto r = o1->GetComponent<Renderable>();
    //auto p = o1->GetComponent<Transform>();

    s->Update();
    o1->RemoveComponent<Transform>();
    s->Update();

    return 0;
}
Skoro naletim na ovaj intersantan tred (zadnji kod tag), gde se unikatan id po tipu (kompnenti) moze dobiti na osnovu adrese funkcije oslanjajuci se na "one definition rule".
Znaci imam GameObject koji sadrzi listu unikatnih komponenti koristeci unordered_map, gde je key size_t tip (koristeci type_id funkciju iz navedenog treda) a value je shared_ptr Component (bazna klasa svih komponenti), dodavanje/pristup komponenti bi trebalo biti veoma efikasno.
Onda imam "sisteme" koji operisu na specificnim komponentama (RenderSystem, PhysicsSystem...) i SceneManager koji azurira ove sisteme i kreira objekte.
final kljucnu rec na izvedene klase sam koristio jer sam cuo da moze biti hint kompajleru da uradi devirtualizaciju a i ne ocekujem neko daljnje nasledjivanje.

Ne svidja mi se sto sam morao da embedujem pokazivac na SceneManager u sam GameObject jer moram nekako da ubacim komponente i u sisteme, a ne bih da menjam "sintaksu" koriscenja ovog sistema.
Da li vidite neki fejl u kodu, da li ste radili nesto slicno, imate neke predloge?

Posto sam radio u VS 2013 okruzenju, a zna se da VS nije uvek u saglasnosti sa c++ standardom, moze da se desiti da kod ne bude radio sa drugim kompajlerima.
Belphegor je offline   Odgovor sa citatom ove poruke