Verweise auf andere Entities
-
Zuerst würde ich Objekte nicht sofort löschen. Zwischen "Einheit stirbt" und "Einheit verschwindet" vergeht ja einige Zeit (Sterbeanimation). Die Einheiten können außerdem einen Referenzzähler bekommen. Jedes Objekt, dass ein anderes Objekt referenziert, erhöht den Referenzzähler dieses Objekts und verringert ihn wieder sobald die Referenz weggenommen wird. Das letzte Objekt, dass die Referenz löscht, löscht auch das Objekt (mit einem Weltobjekt, dass Referenzen auf alle nicht toten Objekte hat um ihre Löschung zu verhindern).
-
Mechanics schrieb:
Die KI könnte vielleicht intern auf das Blackboard Pattern setzen und eine Komponente müsste über Events benachrichtigt werden. Hier würde sich schon ein Observer anbieten.
Ja, generell bietet einem Observer recht viele Möglichkeiten als Event-System. Vielleicht kann man die Löschung von Verweisen gerade als speziellen Event modellieren... Vorläufig werde ich wohl diesen Ansatz ausprobieren, eventuell melde ich mich wieder.
Mechanics schrieb:
Was hat das alles für einen Sinn? Zufallszahlen sollte man ganz sicher nicht brauchen und auch nicht verwenden.
Ich finde die Idee gar nicht so schlecht, allerdings könnte man fortlaufende Ganzzahlen statt Zufallszahlen verwenden, um Kollisionen zu vermeiden.
Und bitte verwendet nicht gleich Full-Quotes, besonders wenn der zitierte Beitrag gerade darüber steht
nwp3 schrieb:
Zuerst würde ich Objekte nicht sofort löschen. Zwischen "Einheit stirbt" und "Einheit verschwindet" vergeht ja einige Zeit (Sterbeanimation).
Ja, bisher hatte ich auch meist ein Flag gesetzt und in regelmässigen Abständen alle markierten Objekte entfernt. Eigentlich können die Verweise schon zum Todeszeitpunkt (vor dem Löschzeitpunkt) entfernt werden, falls keine Interaktion mit toten Objekten mehr stattfindet.
nwp3 schrieb:
Die Einheiten können außerdem einen Referenzzähler bekommen. Jedes Objekt, dass ein anderes Objekt referenziert, erhöht den Referenzzähler dieses Objekts und verringert ihn wieder sobald die Referenz weggenommen wird. Das letzte Objekt, dass die Referenz löscht, löscht auch das Objekt (mit einem Weltobjekt, dass Referenzen auf alle nicht toten Objekte hat um ihre Löschung zu verhindern).
Das wäre im Prinzip der
std::shared_ptr
-Ansatz, nur von Hand programmiert.
-
Nexus schrieb:
nwp3 schrieb:
Die Einheiten können außerdem einen Referenzzähler bekommen. (...)
Das wäre im Prinzip der
std::shared_ptr
-Ansatz, nur von Hand programmiert.Das wäre der intrusive_ptr Ansatz. Und der hat u.A. den Vorteil dass man nicht für die Thread-Safety bezahlt (InterlockedIncrement) die man gar nicht braucht.
-
Stimmt, an
boost::intrusive_ptr
habe ich gar nicht gedacht, danke für den Hinweis.
-
Wobei das (Ref-Counting/intrusive_ptr) mMn. den Nachteil hat, dass man u.U. Objekte am Leben erhält die eigentlich schon weg sein sollten.
-
Ich habe bisher gute Erfahrungen mit
boost::signal
und dem Verzicht auf shared ownership gemacht. Signal ist die Implementation des Observation Pattern in C++ und es gibt keine guten Argumente dagegen. Den resultierenden, verständlichen Code bezahle ich gerne mit einem leicht höheren Ressourcenbedarf.Shared ownership wie mit
shared_ptr
oderintrusive_ptr
ist meistens ein Designfehler, der zu zyklischen Abhängigkeiten und seltsamen Bugs führt (man verliert leicht den Überblick über die gerade vorhandenen Zeiger).
weak_ptr
sollte nicht missbraucht werden, um zerstörte Objekte irgendwann irgendwo auszutragen. Das ist ein unlogischer Hack.Außerdem trenne ich das inhaltliche Verschwinden eines Spielobjektes von der Zerstörung des C++-Objektes. Das gehört schließlich zum normalen Verhalten des Objektes und nicht zur Ressourcenverwaltung. RAII ist nicht das geeignete Mittel, um andere Objekte zu benachrichtigen. Was tun mit einer Ausnahme im Destruktor? Es gibt keine Möglichkeit, die weiterzureichen. Wie verhindern, dass sich das Programm beim Beenden unnötig mit Benachrichtigungen aufhält oder dass unerwünschte Seiteneffekte auftreten?
Wenn ein Objekt in der Spielwelt ungültig wird, ruft es ein Signal auf, bei dem sich zuvor alle Beobachter des Objektes eingetragen haben. Die können sofort angemessen reagieren. Ein Beobachter muss sich nur an die einfache Regel halten, dass das beobachtete Objekt nach dem abgeschlossenen Aufruf des Signals nicht mehr benutzt werden darf. Wer das beobachtete Objekt besitzt, kann dem Beobachter egal sein. Zwei Objekte können sich problemlos gegenseitig beobachten, das hat keinen Einfluss auf die Lebenszeiten.
Aus solchen einfachen Überlegungen ergibt sich fast von selbst das gesamte Design. Ganz ohne
shared_ptr
oder andere Hacks.shared_ptr
hat durchaus seine Anwendungen, siehe Completion Handler in Boost Asio. Da braucht man Wertsemantik und Thread-Sicherheit ->shared_ptr
. Bei Beobachtern und Beobachteten hat der aber nichts zu suchen.
-
TyRoXx schrieb:
Ich habe bisher gute Erfahrungen mit
boost::signal
und dem Verzicht und shared ownership gemacht. Signal ist die Implementation des Observation Pattern in C++ und es gibt keine guten Argumente dagegen.Doch, dass es nicht threadsafe ist. In diesem Fall egal, in anderen nicht.
Und genau da kommt auch schon der Fall daher wo man doch weak_ptr beim Observer-Pattern braucht: wenn die Löschung von Objekten gleichzeitig mit dem Feuern von Events in unterschiedlichen Threads passieren kann.
Dann muss man beim Auslösen des Events nämlich ca. sowas machen:for (auto entry : blah) if (shared_ptr<Observer> o = entry.weakObserver.lock()) o->Notify();
Wobei es natürlich vorteilhaft wäre den Fall (gleichzeitiges Löschen und Feuern von Events) ganz zu vermeiden. Was vermutlich in den meisten Fällen auch (sinnvoll) möglich ist.
-
hustbaer schrieb:
Und genau da kommt auch schon der Fall daher wo man doch weak_ptr beim Observer-Pattern braucht: wenn die Löschung von Objekten gleichzeitig mit dem Feuern von Events in unterschiedlichen Threads passieren kann.
Dann muss man beim Auslösen des Events nämlich ca. sowas machen:for (auto entry : blah) if (shared_ptr<Observer> o = entry.weakObserver.lock()) o->Notify();
Boost.Signals2 sollte so etwas erlauben, wenn man es denn braucht.
Dassshared_ptr
undweak_ptr
bei der Implementation von Signal/2 nützlich sind, will ich gar nicht bestreiten. In der eigentlichen Anwendung haben sie aber selten eine Daseinsberechtigung.
-
Boost.Signals2 ist auch ein gutes Stichwort. Ich habe nur vor langer Zeit einmal Signals1 verwendet, müsste mich wieder genau einlesen.
hustbaer, wurde Signals2 nicht vor allem mit der Motivation entwickelt, threadsicher zu sein?
-
Nexus schrieb:
Boost.Signals2 ist auch ein gutes Stichwort. Ich habe nur vor langer Zeit einmal Signals1 verwendet, müsste mich wieder genau einlesen.
hustbaer, wurde Signals2 nicht vor allem mit der Motivation entwickelt, threadsicher zu sein?
Soweit ich das in Erinnerung habe: ja.
Es was aber die Rede von "signals" (=signals1)