Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

buggypixels

Treue Seele

Beiträge: 125

Wohnort: Meerbusch

Beruf: Programmierer

  • Private Nachricht senden

21

12.04.2011, 10:21

Ich würde erst einmal einen Handle definieren:

C-/C++-Quelltext

1
2
3
4
5
6
struct UnitHandle {
  UnitHandle() : m_Index(0) , m_Type(0) {}
  UnitHandle(int index,int type) : m_Index(index) , m_Type(type) {}
 uint m_Index : 28;
 uint m_Type : 4;
};


Damit hast Du den Index und den Typen direkt zusammen und auch direkt einen etwas besseren "Namen" oder Bezeichung als "uint".
Bei Bedarf kannst Du auch mehr Informationen oben in den Handle reinnehmen.

Dann in Deinem Code machst Du etwas wie:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
class UnitController {
public:
  UnitHandle create(xxxxx);
  void update(const UnitHandle& handle,xxx);
private:
  Unit* m_Units[4096];
  int m_Counter;
};


Create sieht dann so aus:

C-/C++-Quelltext

1
2
3
4
5
6
Unit* u = new Unit();
xxxxx was Du so alles machen willst
int index= m_Counter;
m_Units[index] = u;
++m_Counter;
return UnitHandle(index,1);


und Update ist dann:

C-/C++-Quelltext

1
2
3
4
void update(const UnitHandle& handle,xxx) {
  Unit* u =  m_Units[handle.m_Index];
  // und nu mach was mit u
}

22

12.04.2011, 10:34

@BlueCobold:

also im prinzip will ich, dass man nur mittels des controllers einheiten erstellen kann und werte setzen kann. das soll nicht direkt über die unitklasse von außen gehen. get methoden sind egal, da dort nur der wert abgefragt wird, aber nichts geändert wird.
mir kam die idee, anstatt eben const Unit& zurückzugeben, nur mehr die id zurückzugeben beim create, und dann statt den const unit& parametern, gibt man dann nur mehr eine unitid an. dazu bräuchte ich halt dann auch die getfunktionalität im controller, was ihn imo unnötig aufbläst( glaube so ähnlich läuft das mit dem DarkGDK ab. da arbeitet man ja auch nur über ids der objekte).

@buggypixels:
wenn ich dich richtig verstehe, meinst du, dass ich nach außen nur unithandles gebe, die im prinzip dann auf eine unit verweisen, selbst aber nicht wirklich viel können. verstehe jedoch das hier nicht:
uint m_index : 28
uint m_Type :4

was genau bedeutet das? warum 28, warum 4? was soll der type darstellen?

ich könnte im unithandle dann einen privaten Unit* pointer mitspeichern und dadurch die getmethoden dann ins unithandle packen. dann kann man die vom unithandle aus aufrufen, ohne über den controller gehen zu müssen.

ist schonmal ein interessanter ansatz, aber bitte noch erklärung für diese uint index/type doppelpunkt zeug. das verstehe ich nicht, was das bewirkt.

ansonsten danke für die vielen tipps/denkanstöße.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

23

12.04.2011, 10:41

also im prinzip will ich, dass man nur mittels des controllers einheiten erstellen kann und werte setzen kann. das soll nicht direkt über die unitklasse von außen gehen. get methoden sind egal, da dort nur der wert abgefragt wird, aber nichts geändert wird.

Nur der Vollständigkeit halber: Mit dem Facade Pattern von dem hier ja offenbar bisher die Rede war hat das dann aber nichts mehr zu tun. Das ist jetzt weder gut noch schlecht, einfach nur ne Feststellung.

ist schonmal ein interessanter ansatz, aber bitte noch erklärung für diese uint index/type doppelpunkt zeug. das verstehe ich nicht, was das bewirkt.

Das sind Bitfelder.

Grundsätzlich vielleicht ein kleiner Tip: Verlauf dich nicht in zuviel Design ;)

buggypixels

Treue Seele

Beiträge: 125

Wohnort: Meerbusch

Beruf: Programmierer

  • Private Nachricht senden

24

12.04.2011, 10:43

Das ist ein besonderer Konstrukt um so etwas wie ein Bitfeld zu definieren. In diesem Fall bedeutet es, dass Du einen uint hast. Dieser besteht aus 32 Bits.
Die ersten 28 Bits sind der Index und die restlichen 4 der Typ. Das mit dem Typ ist nur ein Vorschlag, falls Du verschiedene Unit Typen hast (Gegenstände oder was auch immer). Dann hast Du die beiden Informationen Index und Typ der Unit in einem 32 Bit unsigned int zusammen.
Dieser UnitHandle ist nur dazu da, um schnell per handle.m_Index direkt auf das Array mit den eigentlichen Unit Objekten zuzugreifen.
Anderes Beispiel:

C-/C++-Quelltext

1
2
3
4
5
class Unit {
  void setPosition(const Vec3& pos);
private:
  Vec3 pos;
};

Also Deine Unit hat eine Methode, um die Position zu setzen. Dann machst Du im UnitController:

C-/C++-Quelltext

1
2
3
void setPosition(const UnitHandle& handle,const Vec3& pos) {
  m_Units[handle.m_index]->setPosition(pos);
}

Der UnitHandle ist nicht anderes als Deine ID nur etwas aufgebohrt.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

25

12.04.2011, 10:46

Alternativ könntest du z.B. auch einfach mit Unit* bzw Unit& arbeiten und den Typ Unit nach außen hin inkomplett lassen, d.h. auf der anderen Seite deiner Schnittstelle gibts nur das:

C-/C++-Quelltext

1
class Unit;

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

26

12.04.2011, 10:55

Jup. Das kommt dem Vorschlag mit dem Interface sehr entgegen, nur eben auf eine sehr radikale Art ;)

Edit:
Allerdings eröffnet sich mir die Frage, WARUM alles über den Controller laufen soll. Und wenn das schon der Fall sein soll, warum dann nicht transparent derart, dass die set-Methoden der Units eben DOCH verwendbar sind, diese aber über das Observer-Pattern alle Änderungen an den Controller mitteilen.
Das würde die Objekt-Orientierung nicht in einen pseudo-imperativen Ansatz umändern und wäre vollkommen transparent und "ordentlich". Das könnte ebenfalls das "delete"-Problem prima behandeln.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

27

12.04.2011, 11:03

Factories, Fassaden, Controller, Handles...am Besten ist es mal einen Schritt zurück zu tun und sich ganz ehrlich und objektiv anzuschauen was davon im gegebenen Kontext wirklich notwendig ist. Warum brauch ich den ganzen Kram, was hab ich davon und warum kann ich nicht einfach new Thingie() machen und Glücklich sein?

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

28

12.04.2011, 11:18

Jup, genau das ist es, worauf ich mit der Frage abziele. Es wirkt hier so, als soll irgendein ein Pattern oder Konzept verwendet werden, ohne dass es wirklich notwendig wäre.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

buggypixels

Treue Seele

Beiträge: 125

Wohnort: Meerbusch

Beruf: Programmierer

  • Private Nachricht senden

29

12.04.2011, 12:43

Alternativ könntest du z.B. auch einfach mit Unit* bzw Unit& arbeiten und den Typ Unit nach außen hin inkomplett lassen, d.h. auf der anderen Seite deiner Schnittstelle gibts nur das:

C-/C++-Quelltext

1
class Unit;


Das könnte er natürlich. Aber dann stellt sich wieder die Frage, wer kümmert sich um das Life-Cycle Management, oder einfach gesagt, wer kümmert sich um das "delete". Wenn es nur eine Factory ist, dann ist das klar. Die Factory erstellt ja nur Instanzen und nichts weiter.
Aber der Punkt mit dem Handle hat noch einen anderen Aspekt. Man könnte nämlich alle Methoden der Unit Klasse in den Controller überführen.
Also statt

C-/C++-Quelltext

1
2
3
4
5
6
class Unit {
  void setPosition(const Vec3& pos);
  void setState(const bool& active);
private:
  Vec3 pos;
  bool active;

im Controller ein Array haben mit diesen Vec3 als Position und für active:

C-/C++-Quelltext

1
2
3
4
5
6
7
class UnitController {
  void setPosition(const UnitHandle& handle,const Vec3& pos);
  void setState(const UnitHandle& handle,const bool& state);
private:
  bool* m_Active;
  Vec3* m_Positions;
}


Dann könnte eine Update Methode im Controller für alle Units so aussehen:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
void update() {
  bool* active = m_Active;
  Vec3* pos = m_Positions;
  for ( int i = 0; i < m_Counter; ++i ) {
    if ( *active ) {
       *pos += m_Velcoity;
    }
  }
  ++pos;
  ++active;
}

Das hat den Vorteil, dass die Daten koherent im Cache liegen und somit die Performance erheblich verbessert werden kann.
Das ist zwar für den ein oder anderen kein "schönes" OO-Design mehr, bringt aber extrem viel Performance.
Gerade bei der Spieleprogrammierung geht es ja um Performance und nicht um den Schönheitspreis für das beste Design.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

30

12.04.2011, 12:53

Es geht bei Performance aber nie um Premature Optimization und genau das machst Du da.

Man sollte zunächst IMMER erst auf gutes Design achten und die Verwendung guter Algorithmen, bevor man anfängt Dark Voodoo zu benutzen, um aus 51% Performance 52% zu machen. Speziell dann, wenn die Optimierung selbst
1) nicht viel bringt
und
2) dem Compiler überlassen werden kann. Sonst könnten wir ja gleich wieder anfangen Assembler zu benutzen (obwohl das bei all dem Pipelining heute wohl doch lieber einem guten Compiler überlassen werden sollte).
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Werbeanzeige