[C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Schrompf
Moderator
Beiträge: 4884
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

[C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Beitrag von Schrompf »

Hallo Leute,

sorry für den reichlich vagen Diskussionstitel. Ich habe folgendes Problem:

- Spielobjekte können für verschiedene Zwecke Skripte haben
- Skripte können sich zur Laufzeit ändern
- Objekte können kommen und verschwinden

Soweit, so klassisch. Jeder Skriptbesitzer meldet jetzt seinen Skript-Quelltext bei einer zentralen Sammelstelle, die alle Skripte zusammenfasst, den Boilerplate-Code drumrum erzeugt und das ganze kompiliert. Jetzt kann die Sammelstelle die Skriptfunktion-Handles extrahieren und den ursprünglichen Skriptbesitzern zukommen lassen. Wenn ein Objekt seinen Skriptcode ändert oder ein neues Objekt mit Skriptcode dazukommt, muss alles neu kompiliert werden und alle Handles müssen den ursprünglichen Besitzern neu zugestellt werden, weil sie sich potentiell geändert haben.

Und da ist der Haken: das passiert aktuell über einen Zeiger, den jeder Skriptbesitzer zusammen mit seinem Quelltext abgibt. In den meisten Fällen klappt das, und eigentlich melde ich auch in den Destruktoren immer den Skriptcode wieder ab, aber irgendwas habe ich anscheinend übersehen. Ich kriege speziell in einem Level, in dem ich viele Objekte mit speziellen Skripten während des laufenden Spiels erzeuge, immer wieder absonderliche Crashes, Fehlverhalten oder viele Warnungen von Visual Studio, dass ich Speicher nach dem Freigeben verändert hätte.

Nun war ich schon drauf und dran, mir eine kleine Handle-Klasse zu schreiben, die den Zeiger verwaltet und das An- und Abmelden übernimmt. Aber dabei ist mir aufgefallen, dass es ja schon einiges an fertigem Verhalten gibt, das so ähnlich läuft. Sei es nun std::future<>, das man sicher auch für nicht-threaded-Situationen nutzen kann, oder sei es std::shared_ptr... Daher nun meine Frage an die hiesigen Experten: wie würde man eine solche Situation idiomatisch und sauber lösen?

Danke schonmal für eure Zeit.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Ingrater
Establishment
Beiträge: 103
Registriert: 18.04.2007, 21:52

Re: [C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Beitrag von Ingrater »

Ich würde da wie üblich vorgehen. "Adding another level of indirection solves every problem".

Ich würde eine art Handle-Klasse erzeugen die sich intern ein globales Array hält. In diesem Array werden die eigentlichen Skript-Handles referenziert. Neben dem pointer merkst du dir dann noch einen Zähler für jeden Eintrag im Array, der jedes mal dann hochgezählt wird, wenn der Eintrag neu vergeben wird. Ein handle besteht dann nur aus einem index in dieses globale Array und dem wert des Zählers der Vorlag als dieses Handle erzeugt wurde. Beim Zugriff über das Handle überprüfst du dann ob der Zähler im Handle noch dem Zähler im globalen Array (am entsprechenden Index) entspricht, wenn nicht ist das Handle inzwischen invalide. Das reloaden von Skripten ist dann ebenfalls trivial, da du nur an einer einzigen Stelle (im globalen Array) das Skript-Handle austauschen must. Das globale Array kann dann nach belieben in der größe geändert werden da du ja nur indices dafür speicherst und keine direkten pointer. Außerdem kannst du durch die Zähler Einträge im globalen Array wiederverwenden die nicht länger verwendet werde ohne plötzlich alte Handles wieder in einen validen Zustand zu versetzten. Ich mache das mit allen meine Resourcen so und das hat mir das Leben deutlich vereinfacht. Ich hoffe die Erklärung war verständlich.
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4263
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: [C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Beitrag von Chromanoid »

Mal ein paar Fragen :):
1. Single-Threaded?
2. Wie wird der Zustand/Stack(?) des Scripts gehalten und muss dieser auch gelöscht werden wenn der Script sich ändert?
3. Ist das ganze im Grunde nur eine Ressourcenverwaltung von Ressourcen, die sich verändern können oder steckt mehr dahinter?
Benutzeravatar
Schrompf
Moderator
Beiträge: 4884
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Beitrag von Schrompf »

@Ingrater: das ist eine gute Idee. Anstatt die Zeiger quer durch den Speicher an die Besitzer zurück zu reichen, kann ich auch einfach einen Array-Index zurückreichen und die Besitzer holen sich dann unter dem Index den Zeiger, wenn sie ihn brauchen.

@Chromanoid:
1. Ja, Single Threaded
2. Wir reden hier nur von Kompilat, alle Skripte sind bewusst ohne Laufzeitzustand gehalten
3. Es ist eine Resourcen-Verwaltung, bei der sich alle Resourcen mit verändern, wenn eine Resource geändert wird

Mir schien halt, dass ich einfach nur einen shared_ptr neu erfinde, wenn ich anfange, Verweise zu tracken. Das kann ja nun auch nicht die Lösung sein. Da gefällt mir so ein klares Besitzverhältnis wie von Ingrater vorgeschlagen doch besser.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4263
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: [C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Beitrag von Chromanoid »

Ah verstehe.
Ja, das mit den Indizes ist ne gute Lösung. Eine Referenzklasse (kein Pointer), die automatisch den Counter runterzählt, wenn das Objekt (das den Script benötigt) abgeräumt wird, würde vielleicht für noch etwas mehr Komfort sorgen. Sowas wäre doch dann etwas RAII mäßiger oder? :)
Wenn die Skripte immer unter einem Key, Dateipfad o.Ä. angefordert werden, dann könnte man auch diesen Schlüssel für die Scriptregistratur verwenden (vorausgesetzt die Schlüssel sind effizient genug).
Benutzeravatar
Schrompf
Moderator
Beiträge: 4884
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Beitrag von Schrompf »

Ja, so ein automatisches Handle wär ne saubere Sache. Aber genau das ist doch dann eine Neuerfindung von shared_ptr, oder?

Effizienz ist hier nicht so wichtig - es gibt bestenfalls ein paar hundert Skripte, und das Zusammenstellen und Kompilieren des Quelltextes kostet mehr Rechenzeit als alle Verwaltung. Ich habe es jetzt wie von Ingrater vorgeschlagen: die Skriptcode-Meldefunktion reicht ein Handle zurück, über das die Objekte sich dann die eigentliche Skriptfunktion holen, wenn sie das Skript ausführen wollen. Ich habe da dann gleich eine kleine ID und den kompletten Skriptcode als Key in eine std::map genommen und so automatisches Zusammenfassen von Skriptcode bekommen... statt 20k Zeilen sind es jetzt noch ein paar Hundert.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4263
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: [C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Beitrag von Chromanoid »

Schrompf hat geschrieben:Ja, so ein automatisches Handle wär ne saubere Sache. Aber genau das ist doch dann eine Neuerfindung von shared_ptr, oder?
Ja, im Grunde hast Du recht. Ich bin ja kein C++ Programmierer, aber es müsste dann eigentlich so aussehen:
* Jedes geskriptete Objekt hat einen shared_ptr<HandleContainer> und Dein Manager eine map<Key,weak_ptr<HandleContainer>>, so würdest Du Dir das manuelle Zählen sparen können.
* Der HandleContainer müsste sich im Destruktor selbst aus der Map löschen, was vielleicht unschön ist...
Der Aufbau würde natürlich dafür sorgen, dass Scripts auch gelöscht werden, wenn sie keiner mehr braucht. Vielleicht ist das auch blöd, wenn der Script 1 min später eh wieder im Level benötigt wird. Das sollte man dann aber eher über einen Manager mit Cache abfangen, der die eigentlichen Scripte kompiliert und ggf. für eine gewisse Zeit vorhält.
Benutzeravatar
Ingrater
Establishment
Beiträge: 103
Registriert: 18.04.2007, 21:52

Re: [C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Beitrag von Ingrater »

Ich kann nur stark davon abraten shared_ptr dafür zu verwenden. shared_ptr implementiert reference counting und die lebenszeit von resourcen hart an einen Reference count zu binden ist meiner Meinung nach die schlechteste Design Entscheidung die man treffen kann. Und ich spreche da aus Erfahrung.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] wie clever umgehen mit zeitverzögerten Ergebnissen

Beitrag von CodingCat »

Ohne mich jetzt besonders in den Thread eingelesen zu haben: Zu shared_ptr gibt es selbstverständlich einen weak_ptr, und das weiter oben beschriebene Invalidierungsverhalten klingt ein wenig, als könnte es mit dem weak_ptr-Verhalten übereinstimmen.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Antworten