Wie gesagt, anhand der dürftigen Beschreibung kann man es nur vermuten. Aber wenn ich schon pushEvent lese, dann kann das nicht passen.
DOD bedeutet doch im Grunde, dass man genau die Daten direkt im Speicher beieinander hat, die man gerade genau für die Berechnung braucht.
Alleine die Signatur Deiner Methode läßt da Zweifel aufkommen. Wenn das noch Events in Spiel kommen und du eventuell dann wahllos dadurch
im Code umher springst, dann ist eh alles verloren.
Aber wie gesagt, man kann es nur vermuten. Oder Deine Beispiele sind einfach unglücklich gewählt.
oO Also entweder kann man nur vermuten oder man sagt "das ist nicht DOD"
Die Daten sind in der Tat cache-freundlich angeordnet und die internen/privaten Methoden enthalten die Logik die auf den Daten ausgeführt wird... Und die übergeordnete Logik (z.B. vor einer Bewegung wird ein Kollisionstest ausgeführt) steckt in der update()-Methode.
Deine Pauschalisierung bzgl. Events kann ich überhaupt nicht verstehen. Warum ist alles verloren? Warum spring ich wahllos im Code herum? Wie gesagt: Das Physik-System an sich arbeitet intern in Anlehnung an DOD, die öffentliche Schnittstelle ist objektorientierter und arbeitet (aus Gründen der losen Kopplung verschiedener Systeme) über Events (anstelle von direct dispatch etc.). Ws ist daran wahllos gesprungen oder gar verloren?
Und meine grundlegende Frage ist, wie ich meine DOD-basierten Methoden am besten teste. Da ich sie nur innerhalb des Systems aufrufe sehe ich eigentlich keinen Grund sie prinzipiell öffentlich zu machen. Die Implementierungsdetails durch die update()-API zu testen finde ich etwas zu aufwändig...
Hier mal eine (vereinfachte) Skizze des Systems (um Missverständnisse zu vermeiden):
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
struct PhysicsData {
unsigned int id;
unsigned int x, y; // pos
bool collideable;
int move_x, move_y; // movement vector
};
struct PhysicsEvent {
unsigned int id;
int move_x, move_y;
};
class PhysicsSystem {
private:
std::vector<PhysicsData> components;
std::vector<PhysicsEvent> incomming;
// liefert true falls objekt auf (x,y) kollidieren würde (mit terrain oder anderem objekt)
bool doesCollide(PhysicsData const & data, unsigned int x, unsigned int y) const {
// ...
}
// führt Bewegungsinterpolation aus
void interpolate(PhysicsData& data, int elapsed_time) {
// ...
}
public:
void push(PhysicsEvent const & event) {
incomming.push_back(event);
}
void update(int elapsed_time) {
// handle events
for (auto const & ev: incomming) {
auto& obj = components.at(ev.id); // Zuordnung via ID in echt anders ^^
if (!doesCollide(obj, ev.move_x, ev.move_y) {
// apply event
obj.move_x = ev.move_x;
obj.move_y = ev.move_y;
}
}
incomming.clear();
// interpolate object components
for (auto& obj: components) {
if (obj.move_x == 0 && obj.move_y == 0) {
continue;
}
interpolate(obj, elapsed_time);
}
}
};
|
Und in jedem Frame wird das System aktualisiert: Neue Physik-Events (um Bewegungen auszulösen) werden propagiert und das System aktualisiert (was den Rest auslöst). Daher ist das gesamte Innenleben schön gekapselt und ich kann das System als Blackbox verwenden: Events propagieren + System aktualisieren.
Dazu kommen noch div. andere Unterfunktionen die ich hier natürlich jetzt nicht alle aufliste
Aber z.B. die Interpolation: Die würde ich gerne Testen. Dazu müsste ich sie entweder public machen oder über die push-update-API arbeiten, d.h.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
|
PhysicsEvent ev;
ev.id = 13;
ev.move_x = 1;
ev.move_y = -1;
my_physics_system.push(ev);
my_physics_system.update(50); // z.B. 50ms
// und dann über die (hier weggelassene restliche API)
auto& data = my_physics_system.query(13);
// prüfen ob sich data.x, data.y richtig geändert haben etc.
|
Das bläht mir allerdings die Testfälle ziemlich auf.
Die andere Variante (interpolate() öffentlich) halte ich für nicht richtungsweisend ... aus meiner Sicht ist das Teil der internen Struktur des Systems und sollte nicht öffentlich sein. Zur Komunikation zwischen den Systemen (Beispiel: KI-System will für Objekt X eine Bewegung starten --> erzeugt PhysicsEvent und kennt einen PhysicsEventListener - ohne das ganze PhysicsSystem kennen zu müssen) verwende ich bereits die Events. Jetzt noch die restlichen Teile (die durch die Events aufgerufen werden) öffentlich zu machen wirkt auf mich dabei äußerst fragwürdig.
So, ich hoffe nun ist das ganze weniger unklar
LG Glocke
/EDIT: Im Übrigen wäre vllt. denkbar in entfernter Anlehnung bzw. Inspiration an das Pimpl-Idiom zu arbeiten (oder eher: Die Implementierungsdetails auslagern). Konkret: Die Unterroutinen (Bewegungsinterpolation etc.) von den Systemen loslösen und in einem internen Namespace als alleinstehende Funktionen ansammeln, im Stile:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
|
namespace impl {
void interpolate(PhysicsData& data, Scene& scene, int elapsed_time) {
// ...
}
}
|
Das ganze würde natürlich bedeuten, dass zusätzliche Parameter nötig sind. Beispiel: In meiner Spielszene habe ich zusätzlich ein Kollisionsgrid in dem steht, ob sich auf Position (x,y) eine Wand, ein Objekt etc. befindet. Diese Szene müsste ich beim interpolieren natürlich mit übergeben, wenn ich die Interpolation aus dem System rauslöse, da das PhysikSystem die zugehörige Szene kennt - die alleinstehende Funktion aber nicht (mehr). Das Objekt besitzt weiter die ID der zugehörigen Scene, d.h. nicht die Scene selbst.
Das ganze würde die Komplexität beim Aufruf innerhalb des Physik-Systems nicht wirklich steigern - es müssen nur die zusätzlichen Parameter übergeben werden. Und beim Testen kann ich mir eine Scene und ein Objekt zurecht legen und die Interpolation extrem einfach testen... Was denkt ihr?
/EDIT2: Oder noch "einfacher":
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
namespace priv {
struct PhysicsImpl {
PhysicsImpl(/* ... */) {
// ähnliche Konstruktion wie PhysicsSystem, so dass es die Scene(n) bereits kennt
}
void interpolate(PhysicsData& data, int elapsed_time) {
// ...
}
};
} // ::priv
class PhysicsSystem: private priv::PhysicsImpl {
// ähnlich oben, nur ohne die Unterroutinen
};
|
Dann lassen sich PhysicsImpl mittels einfachem UnitTest
|
C-/C++-Quelltext
|
1
2
3
4
5
|
PhysicsData data;
// setup object
priv::PhysicsImpl impl(/* setup up */);
impl.interpolate(data, 50);
// check data
|
und PhysicsSystem mittels Integrationstest (in Zusammenarbeit mit anderen Systemen testen - push&update).