Objektmanagement sinnvoll gestalten
-
hustbaer schrieb:
rapso schrieb:
schau dir mal component based entities an, so sollte man das heute eher machen.
Ich hab mir dazu ein paar Dinge im Inet durchgelesen (u.A. auch deine Links). Was mir dabei nicht ganz klar ist (weil ich nirgends ein Beispiel dazu gefunden habe - die Beschreibungen drücken sich da alle recht elegant drumrum)... wie lässt man Components interagieren?
Also das was in deinem Link kurz unter "Closing Thoughts" (letzte Seite) angeschnitten wird.das haengt stark davon ab, welche daten und wie oft man uebermitteln moechte. oft wird ein event/message/signal system benutzt, wenn z.b. der player stirbt, wird ein event dafuer getriggert, alle elemente die sich fuer so ein event regestriert haben, bekommen das mit. im einfachsten fall gibt es callback von libsig.
pointer sharen oder desgleichen ist grundsaetzlich nicht der richtige weg, da theoretisch alle systeme unabhaengig laufen koennen, dabei muss es nicht multithreaded sein, es reicht auch dass z.b. in einem RTS die logik fuer die AI componenten time-sliced ist und die updated dann z.b. mit 0.1Hz . bis da jemand mitbekommen wuerde dass ein pointer invalid ist, koennte dieser schonwieder mit was ganz anderem belegt sein.
in einem hypothetisch perfekten system, wuerde jedes entity nur eine ID sein (perfecterweise variable bit length), beim erstellen wuerde es eine object description geben (ich hatte das mal mit xml gebastelt), und dann wird durch alle system die regestriert sind durchgegangen und die jeweiligen descriptoren, fall vorhanden, wuerden in jedem system ihre components, fuer diese entity ID erstellen.
so kannst du wirklich extrem flexibel arbeiten, du kannst z.b. ein system garnicht erst einklinken (z.b. sound-dll loeschen), du kannst zur laufzeit die components eine systems serialisieren (gibt ja keinen pointer drauf ausser im system), das system runterfahren, durch ein anderes ersetzen z.b. ogl->d3d und deserialisieren, das laeuft dann weiter als waere nichts.
du kannst so auch sehr einfach netzwerk kommunikation machen (ich glaube UE3 network code arbeitet zum teil so), du schickst nur events -> die eventqueue repliziert die daten ueber netzwerk (bzw das netzwerk modul ist listener fuer alle events).so perfekt geht das nicht wie man an eurem particle beispiel sieht.
ich wurde schon an code drangesetzt, der so war. ein container wo alle particle sind. entsprechend wurde ich wegen performanceproblemen drangestzt.
- zum einen wurde auf cpu seite pro particle eine virtual function aufgerufen die fuer jeden particle type anders implementiert war, sehr flexibel und ideologisch gut, aber ein vtable lookup und indirekter function call sind schonmal 20mal mehr als die zumeist darin enthaltenen Pos+=Dir;
- auf gpu seite ist es natuerlich visuel auch perfekt, alle particle sind nach entfernung sortiert (kommt natuerlich ein grosser quicksort ueber den ganzen container pro frame hinzu, was anhand der coherenten daten sogar langsammer als ein insert-sort war), pro particle wird die textur, der shader, eine lightquelle und der blendmode gesetzt. selbst mit caching brach das nach spaetestens 3000 particlen total ein (ohne was anderes zu rendern).-loesung 1. ist dann erstmal ein container pro particle system (mit emittern die dorthin spawnen)
cpu seite ist ein grosser switch oder ein vtable call, der sonst pro particle gemacht wurde. war locker 10x schneller in real world. gpu seite hat ein drawcall pro particle system, auch schoen nur ein budget/cap pro system, nachteil ist, dass die ganze welt simuliert wird, was u.U. 99% verschwendung ist (fakeln im ganzen quake level obwohl man nur die in einem raum sieht).
-loesung 2. ist dann pro emitter ein container
cpu seite ist noch relativ fix bei der simulation, culling anhand von AABBs fuers zeichnen und simulieren ist dann einfach, auch collision detection ist relativ gut, da man pro container die paar objekte picken kann die vermutlich kollidieren koennen. gpu seite ist natuerlich auch recht fix und man hat noch die moeglichkeit die emitter zu sortieren.nach der definition von insomniac waere loesung 1 ja noch ein entity system fuer particle. man hat input-transformatin-output. pro system.
particle sind eigentlich die die perfection von component systemen, da man darauf nicht nur an sich transformationen durchfuehrt, sondern ungleich den sonstigen implementierungen wo pro system daten<->logic abhaengigkeit ist, bei particlen einfach nur auf dieselben daten transformationen durchgefuerht werden. z.b. kann man gravitation als ein durchgang machen, collision+reflection als ein pass, sogar incrementeles bubblesort als pass.weil weitere nette links erfragt wurden http://macton.smugmug.com/gallery/8936708_T6zQX#593426709_ZX4pZ
:xmas1:
-
hustbaer schrieb:
Wieso sollten dann alle Fackeln gleich aussehen?
Wenn man für die Fackeln nur ein "Fackel-Feuer" hat, und die Punkte für alle Fackeln in einem Container berechnet werden, sodass man sie nachher rumschieben kann, sehen alle Fackeln genau gleich aus.
-
Ich hätte noch eine Frage zu dem Componenten Basierten Design:
Könnte man nicht auch die GUI somit sinnvoll Realisieren? So das man eine Komponente namens "Interactive" die Ereignisse (Click,Mousover..) realisiert erstellt.
Und dann je nach GUI Element einen ButtonRenderer oder einen InputFieldRenderer einer GUI Entität zuweist.
Somit könnte man die Komponente "Interactive" einen 3D Objekt zuweisen und damit auch mit dem Objekten interagieren. Man müßte nur bei der Ereignesserkennung unterschiedlichen Code verwenden.
Ist dies sinnvoll?
Mit freundlichen Grüßen
Cyphron
-
Nein, GUI würde ich durch Vererbung bauen. GUI einfach keine Vielzahl an Funktionsweisen und kaum Kombinationsmöglichkeiten.
-
Übrigens fällt mir noch eine Sache zum Thema Partikelsystem ein.
Und zwar wenn die Entität mehr als nur einen Emitter benötigt.
Bei einen Düsenjäger mit 2 Triebwerken könnte man ja einfach die Flammen 2 mal versetzt Rendern (und evtl. auch Skaliert).Aber bei unterschiedlichem von selben Typ aber anderen Aussehen (Farbe,Lebenszeit,Dichte...)?
Bei cooky451´s Lösung ist ja eine Komponente innerhalb einer Entität unique.Auf die schnelle würde mir eine Komponente "EmitterList" einfallen die eine Liste von Emittern enthält.
Wie lößt ihr dies?
-
Cyphron schrieb:
Wie lößt ihr dies?
std::multimap. Wollte ich eh nutzen, die Beschränkung auf eine Komponente pro Typ birgt noch mehr Probleme.
Aber ich wäre ja eh mal an einer Beispielimplementierung von rapso interessiert, er scheint das ja noch etwas anders gemacht zu haben. Vielleicht lässt er sich ja hinreißen.
-
Leider habe ich noch Probleme beim umgang mit Smartpointern. Z.B. habe hab ich Probleme einen unique_ptr meiner map hinzuzufügen bzw. den Rawpointer von diesen zu beziehen.
Da man unique Pointer so wie ich verstanden habe nicht kopieren kann habe ich es mit std::move() probiert, jedoch stelle ich mich wohl zu dumm an dafür.
Um an den Rawpointer heran zu kommen hab ich es mit get() versucht. Ebenfalls erhalte ich hier nur Fehler
Könntest du mir zeigen die die die beiden Funktionen getConponent und addComponent implementiert hast?
-
Mit einer multimap wäre es vielleicht sogar sinniger eine Range zurückzugeben, hm.. muss man sich mal was für überlegen. Aber hier die Implementierung, vielleicht hilft es dir ja weiter:
void Entity::addComponent(std::unique_ptr<Component> component) { components_.insert(std::make_pair(component->type(), std::move(component))); }
Component *Entity::getComponent(ComponentType type) const { auto found = components_.find(type); if (found != std::end(components_)) return found->second.get(); return 0; }
-
Vielen dank erstmal.
getComponent funktioniert(mit map.end()) jedoch nicht addComponent.
Fehlermeldung:
c:\mingw\bin\..\lib\gcc\mingw32\4.5.2\include\c++\bits\unique_ptr.h|207|error: deleted function 'std::unique_ptr<_Tp, _Tp_Deleter>::unique_ptr(const std::unique_ptr<_Tp, _Tp_Deleter>&) [with _Tp = Component, _Tp_Deleter = std::default_delete<Component>, std::unique_ptr<_Tp, _Tp_Deleter> = std::unique_ptr<Component>]'|
<<dies ist die selbe Fehlermeldung die mich schon die ganze Zeit plagt
-
Wie rufst du die Funktion denn auf?
-
so wie du:
void Entity::addComponent(unique_ptr<Component> p_Component) { m_Components.insert(std::make_pair(p_Component->getType(),std::move(p_Component)); }
hier meine m_Components definition:
map<string,unique_ptr<Component> > m_Components;
-
Du hast aus Type einen String gemacht? Na ja, wie auch immer, ich meinte eigentlich, wie du die add Funktion aufrufst.
-
Vorerst ein string ja.
Aufrufen tu ich noch gar nichts. Der Fehler tritt beim Compilieren auf. Also bisher existieren nur die Klassen.
-
Hm.. IDEOne rastet auch aus: http://ideone.com/xUQUz
Mir fällt gerade auf, dass ich gar nicht genau weiß, was der Standard da für Movables garantiert.. trotzdem, mein MinGW (4.6.1) macht das ohne zu murren.
-
cooky451 schrieb:
Hm.. IDEOne rastet auch aus: http://ideone.com/xUQUz
Mir fällt gerade auf, dass ich gar nicht genau weiß, was der Standard da für Movables garantiert.. trotzdem, mein MinGW (4.6.1) macht das ohne zu murren.Ich werde mal MinGW updaten.
Edit:
Fehlerfrei durchgelaufen!
evtl ein Bug im MinGW 4.5.2
Edit2:
Auch meine versionen von get/add lassen sich nun compilieren.
Also war ich wohl doch nicht so dumm
-
Erstmal komme ich gut voran. Stehe aber vor einer unschönheit mit meinen Smartpointern.
Ich habe nun einen simplen ResourceManager implementiert:
typedef boost::shared_ptr<Resource> ResourcePtr; class ResourceManager { private: map<string,ResourcePtr > m_Resources; public: ResourceManager(); virtual ~ResourceManager(); void addResource(ResourcePtr p_Resource,string p_ResourceID); void removeResource(string p_ResourceID); ResourcePtr getResource(string p_ResourceID); };
Da die Ressourcen in mehreren Entitäten benötigt werden habe ich shared_ptr anstatt unique_ptr verwendet (mit dem Hilfskonstrukt: "boost::shared_ptr<Resource> ResourcePtr;").
Resource ist eine abstrakte Klasse von der alle Ressourcentypen abgeleitet werden (Models, Texturen, Sounds etc).
So sieht nun meine Ressourcen Initialisierung aus:
initResources() { boost::shared_ptr<Model> tmp(new Model()); tmp->load("hawk.obj"); tmp->test(); m_ResourceManager.addResource(tmp,"Hawk"); //Hat es funktioniert?: Model *tmp2; tmp2=(Model*)m_ResourceManager.getResource("Hawk").get(); tmp2->test(); }
hierbei missfallem diese Zeilen etwas:
Model *tmp2; tmp2=(Model*)m_ResourceManager.getResource("Hawk").get();
Grund ist das ich meiner Entität einen shared_ptr geben möchte damit die Renderer Komponente zugriff auf die Modedaten hat. Dafür werden ja die beiden Zeilen benötigt die mir etwas missfallen. Gibt es vernünftige Alternativen?
-
Über Resource-Management kann man wahrscheinlich drei Bücher schreiben, aber solange du während der Laufzeit nichts hinzufügen oder wegnehmen musst, könntest du auch einfach mit einem unique_ptr für den Manager und einem direkten Pointer / einer Referenz auf die Ressource für die Objekte arbeiten.
Und PS:
Was soll das "hat es funktioniert"? Edit: Und warum hat dein Manager einen virtuellen Destruktor? Und was macht der Konstruktor? Und warum nutzt du nicht std::shared_ptr, falls du den wirklich brauchen solltest.
-
virtueller Destruktor:
ist noch ein überbleibsel von dem was die IDE erstellt hat. Kommt noch weg."Hat es funktioniert":
da schau ich ob ich auf die Resource zugreifen kann. Das kommt später weg (bzw is das nur nen Prototyp, wird später einfach aus nem Verzeichniss/Definitionsdatei geladen).
Aber auf diese weise werde ich wohl in meiner Renderer Componente auf die Daten zugreifen müssen(?).
Edit:
Aber vll mach ich das mit einem Rawpointer. Denn wenn die Resource freigegeben wird aber die Komponente darauf trotzdem zugreifen möchte habe ich eh einen Designfehler.
-
Ach ja, was mir noch aufgefallen ist: Wie wäre es mit
resourcemanager.add(std::unique_ptr<Model>(new Model("model.mdl")));
und der Konstruktor von Model wirft einfach eine Exception? (PS: Etwas offtopic, aber warum gibt's eigentlich kein make_unique? oO)
-
cooky451 schrieb:
Ach ja, was mir noch aufgefallen ist: Wie wäre es mit
resourcemanager.add(std::unique_ptr<Model>(new Model("model.mdl")));
und der Konstruktor von Model wirft einfach eine Exception? (PS: Etwas offtopic, aber warum gibt's eigentlich kein make_unique? oO)
Eine Exception bei was? Edit: File not Found? /Edit
Wenn ich keine shared_ptr benutze kann man es so machen wie du es vorgeschlagen hast. Bzw fällt auch wieder das Hilfskonstrukt weg.
wie meinst du das mit "make_unique"?