Thread-Kommunikation Logik + Renderer

Hi,

ich arbeite zurzeit an einer relativen einfachen Engine. Diese sollte am Ende je einen eigenen Thread für das Rendern und die Logik besitzen.
Auch wenn es noch etwas dauert bis ich tatsächlich das ganze in 2 Threads aufsplitten werde, würde ich gerne jetzt schon das Grundkonzept haben, damit ich meinen COde schon entsprechend schreiben kann.

Mein größtes Problem dabei ist, die Kommunikation der Threads, ich habe schon ein paar Versuche gemacht und etwas herumgesucht, deshalb stelle ich hier mal vor was ich soweit habe, und wo meiner Meinung nach mögliche Probleme liegen könnten. Es würde mich freuen, wenn diejenigen die etwas erfahrung mit Multithreading haben mir dann ein paar Tips oder Verbesserungen etc, geben könnten :).

Der Logik Thread sendet ausschließlich, der Render Thread empfängt.
Übermittelt werden: Position, Roation, Animationsdaten ( z.B.: Animation soll gestartet werden; Skalierungen, etc)

Was ich bei meiner Suche als Kommunikationsmittel für die Threads gefunden habe sind PipedStreams.

Wenn ich nun also im Logik Thread z.B. ein Entity/NPC habe und am Ende des updates dieses meine Daten in den PipedStream screibe sollte das ungefähr so aus sehen:


//x ist Integer wert und muss in seine Bytes zerlegt werden
pout.write(pos.x&0xFF)
pout.write(pos.x&0x00FF)
pout.write(pos.x&0x000FF)       
pout.write(pos.x&0x000000FF)

pout.write(pos.y&0xFF)
pout.write(pos.y&0x00FF)     //Markierung 1
pout.write(pos.y&0x000FF)       
pout.write(pos.y&0x000000FF)

//und so weiter

pout.flush();

Mögliche Probleme :

  1. Ich muss für jedes Entity und den dazugehörigen Rednerer einen PipedStream mit einem begrenzten Puffer anlegen, dies muss dann in einem gemeinsamen Thread passieren
  2. Es könnte passieren dass der InputStream die Daten abfragt während der Outputstream erst einen Teil der Daten hineingeschrieben hat (z.B.: an Markierung 1), die Reihenfolge im Puffer wäre komplett durcheinander, und das Ergebnis wohl absolut unbrauchbar
    3)Ich habe keine Ahnung wie ich mithilfe des Piped Streams übermitteln soll welche Animation gestartet, bzw. gestoppt werden soll

Schon mal vielen Dank fürs durchlesen dieses ganzen Textes :wink:

MfG,
~Oneric

  1. wie kommst du darauf, schon erlebt?
    meiner Ansicht nach als wichtige Grundfunktion natürlich korrekt eingebaut

http://www.certpal.com/blogs/2010/11/using-a-pipedinputstream-and-pipedoutputstream/

The idea behind the piped stream is that at one end of the pipe, a writer thread writes to a PipedOutputStream. A PipedInputStream thread concurrently reads whatever is written on the other side.

edit: ok, Teil von einem int könnte, aber zumindest nicht falsche Reihenfolge,

auf Warten für genug Bytes bis zu interpretierbarer Nachricht musst du eben warten,
auch schon vorher wissen dass ein int kommt und nicht was anderes,
wie entscheidest du sonst ob 4 Bytes zu einem int werden oder nicht?

Hilfsklassen wie DataOutputStream helfen allgemein zu solcher Low-Level-Arbeit,
die Entscheidung, als nächstes einen Char oder einen int zu lesen, muss man aber noch selber treffen

  1. was hat das speziell mit PipedStreams zu tun? macht natürlich Arbeit, alles auszuformlieren, siehe gleich nächster Abschnitt,
    aber sonst nur eine Frage von Angabe an Informationen in korrekten Format, Protokoll:
    „Kommande 589467 [Animation starten], Parameter A=45387980 [zu animierendes Objekt liegt an Archiv-Stelle sowieso]“

allgemein:
Piped oder überhaupt Streams haben ihren Sinn, etwa zwischen zwei Rechnern/ Programmen oder bei (fremden) datenlastigen Formaten
oder noch bei getrennt programmierten Programmteilen,
APIs wie Zip, Verschlüsselung

für dein Programm schreint es dagegen ganz falscher Ansatz,
warum willst du alle Fortschritte einer objektorientierten Sprache aufgeben, zumindest für diesen Teil des Programms, Datenübergabe?
genauso reicht doch eine Methode von Thread zu Thread starteAnimation(Animation xy)

freilich gehört da noch etwas Arbeit dazu, so ein Aufruf legt üblicherweise einen Auftrag irgendwo ab, in einer Liste,
der andere Thread muss regelmäßig darauf hören, es ist auf Synchronisation und evtl. Benachrichtigung zu achten usw.,

das klassische Lehr-Beispiel heißt Producer Consumer:
http://www.tutorialspoint.com/javaexamples/thread_procon.htm

PipedStreams übernehmen einen Teil davon komfortabel, man kann beliebiges ablegen, Synchronisation & Benachrichtigung auch dabei, alles wichtige :wink: ,
aber der Aufwand das zu interpretieren ist auch nicht gering,
selbst wenn manche Auswüchse wie jeden Integer mit 4 Befehlen schreiben gewiss vermeidbar sind
(allein für deinen Code mit x und y würde es sich schon lohnen, eine Hilfsmethode writeInt() anzulegen,
wenn es nicht schon DataOutputStream usw. gäbe)

arbeite mit normalen Java-Mitteln, OHNE Piped, wenn nicht explizit bedacht und gewählt,
dafür dass du das heute kannst, haben sich viele Spracherfinder jahrzehntelang bemüht :wink:

Hi,

danke für die Antwort :slight_smile:

zu 2) stimmt, das kann eigentlich nicht passieren wenn man .flush() (sinnvolerweise) nur am ende aufruft
zu 3) Danke für das Beispiel mit dem Consumer & Producer, das wird mir bestimmt noch helfen

Ich wollte deshalb lauter PipedStream benutzen, da immer wenn ich nach „Java Thread communication“ o.ä. gesucht hatte diese vorgeschlagen wurden.

Ich habe hier mal einen anderen Pseudocode wie es dann vielleicht aussehen könnte, auch mit ein paar Anmerkungen.
Wobei ich eigentlich nicht dabei bleiben will die Animation über ein Enum-Wert zu bestimmen, da manche NPC ja auch ganz spezielle Animationen haben und diese dann alle dort vorhanden sein müssten.

public class Entity {

private final EntityRenderer renderer;

private float x, y, z;

public Entity(/**Parameter */)
{
     renderer = new EntityRenderer();
     RenderThreadReceiver.addNewRenderer(renderer);
}

//Position von außen ändern (teleport)
public void setPos(float x, float y, float z)
{
this.x = x;
this.y = z;
this.z = z;

renderer.updatePos(x, y, z);
}

public void updateTick()
{
//Do stuff

if(//irgnedwas)
   renderer.setAnimation(EntityAnimations.SIT_DOWN); //Enum, wird nur gelesen, nie umgeschrieben, gibt das Probleme ?

//Ganz am ende
renderer.updatePos(this.x, this.y, this.z);
}

public void kill()
{
LogikEntityVerwaltung.deleteEntity();
renderer.setDying(); // Todes-Animation abspielen und dann entfernt werden
}

}
public class EntityRenderer {


privat float x, y, z;
private EntityAnimation anim;

public EntityRenderer() { }

public void render()
{
//Do something
}

public synchronized void updatePos(float x, float y, float z)
{
this.x = x;
this.y = z; //Die Methode könnte jetzt nicht aufgerufen werden, währedn gerade render() ausgeführt wird, oder ?
this.z = z;
}

public synchhronized void setAnimation(EntityAnimation anim)
{
this.anim = animation; //TODO feste(hardcoded) Enum Konstanten durch etwas flexibleres ersetzen
}

public synchronized void setDying()
{
this.anim = EntityAnimation.DIE;
//Wenn Animation fertig: RenderManager.remove(this); ?
}

}

MfG,

~Oneric

von Swing-Komponenten her kennt man Renderer zu Swing-Objekten, für normalen Gebrauch, aber weniger üblich,
auch bereits Swing-Paint anders

eine Klasse wie Entity halte lieber frei von allem anderen, speichert nur x,y,z
langweilig, aber nur ein erste Baustein

und ganz allgemein jetzt, egal welches Konzept du nimmst, selbst wenn Entity eigenen Render zugeordnet hat:
nur Entity bekommt die x,y,z!, übergib die nicht einzeln an Renderer, nicht dort auch noch Variablen, stell dir vor du hättest 15 Variablen…,
das ist noch fast Stream-Niveau,
aber Java ist objektorientiert, übergeben wird EIN Entry-Objekt!
(falls ein spezieller Renderer nicht eh Link auf eigenes zugeordnete Entity hat)

wegen des Updates kann es aber durchaus sinnvoll sein, die Daten doppelt vorzuhalten,
nur nicht in einer zweiten Klasse alle Variablen neu definiert,
sondern falls nötig zwei Entity-Objekte: eins welches der Logik-Thread fast jederzeit bearbeitet,
eins an sicherer Stelle (kurzzeitig Edit verboten, Synchronisation) kopiert und für Zeichenvorgang geschützt vor Änderungen

Vorteil einer simplen universellen Entity-Klasse: deren Verwendungszweck sieht man ihr gar nicht an,
ein drittes Objekt könnte einen Datenbank-Eintrag darstellen, ein viertes in Benutzereingabe-Formular stecken, andere verschiedene Animationsstufen darstellen


die Übergabe von Objekten statt Variablen hat auch andere Vorteile,
du kannst 10 Objekte unterschiedlicher Entity-Klassen in eine Liste schreiben, nacheinander malen usw.,

wobei dann jeweils evtl. eine Renderer-Klasse bestimmt werden muss, eine Unterscheidung muss her, instanceof-Test ist unschön, Enum-Typ besser,
blahblah, so viele Themen…


ein Renderer für 100 Entity dürfte ausreichen, es wird immer nur eine Sache gemalt,
Entitys durchgehen, falls unterschiedliche Typen dann jeweils passenden Renderer auswählen (oder simple Methode kann auch reichen), abarbeiten


//Enum, wird nur gelesen, nie umgeschrieben, gibt das Probleme ?

Problem nicht recht erkennbar, Enum an sich toll, Werte können ja ausgetauscht werden,

wenn an dieser Programmstelle nicht nötig, dann natürlich überflüssig, Verwendung für mich unbekannt