Java Kompatiblität mit GameMaker Buffern

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Java Kompatiblität mit GameMaker Buffern

    Hallo
    In GameMaker kann man "nur" Buffer über das Netzwerk versenden.
    Mein Java Server soll diese Buffer auslesen können. Ich weiß nur nicht, ob sie mit zB. der API ByteBuffer kompatibel ist, oder es andere Möglichkeiten gibt.
    Wisst ihr näheres darüber?
    Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
    Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

    Willst du mit mir auf Discord Chatten/Quatschen?
    Meine Husi's Tutorial Reihe
  • Ich selber habe mich zwar noch nicht mit dem Thema auseinander gesetzt, hab aber den Foreneintrag gefunden:
    forum.yoyogames.com/index.php?…tween-gm-s-and-java.3566/
    Es scheint schonmal möglich zu sein mit Java über ein Netzwerk zu kommunizieren, mehr weiß ich bis jetzt aber auch nicht über das Thema.

    edit: Im Foreneintrag wurde der Buffer einfach so verschickt, sollte also auch mit Java Servern kompatibel sein...
  • Prinzipiell, ja es ist möglich die Buffer auszulesen.
    Allerdings musst du (Wenn die die Daten über TCP from server zum GM client) vorsichtig sein. (TCP versendet keine packete sonder ist ein Datenstrom. Du musst auf der GM Seite also das zusammenfügen von Informationen von den Datenfragmenten selbst durchführen.)

    Zusätzlich verwended der GM eine andere Endianess im Vergleich zu Java, wodurch du zwischen diesen konvertierne musst.
    Ich habe mir für Java eine eigene Klasse geschrieben welche dies bewerkstelligt: (ACHTUNG! Teilweise hässlicher und inneffizienter code. Hab vor das noch zu optimieren oder gegebenenfalls neu zu schreiben.)

    Spoiler anzeigen

    Java-Quellcode

    1. package server.packets;
    2. import java.io.DataInputStream;
    3. import java.io.DataOutputStream;
    4. import java.io.IOException;
    5. import java.nio.ByteBuffer;
    6. import java.nio.ByteOrder;
    7. /**
    8. * Game Makers endianess seems to be different to javas default endianess.
    9. * This class provides functions for reading and writing of inputstreams, while converting
    10. * between BIG_ENDIAN and LITTLE_ENDIAN to ensure a working data transport between the server and the game
    11. * without changes in the information.
    12. *
    13. * TODO: Look into possible optimizations. (Some of the functions allocate objects on the heap
    14. * for the conversion process which could hinder performance.)
    15. *
    16. * @author Lewa
    17. *
    18. */
    19. public class StreamConverter {
    20. public static String buffer_read_string(DataInputStream in,int size) throws IOException{
    21. StringBuilder b = new StringBuilder();
    22. for(int i = 0;i<size;i++){
    23. b.append((char)(in.readByte() & 0xff));
    24. }
    25. return b.toString();
    26. }
    27. public static int buffer_read_u8(DataInputStream in) throws IOException{
    28. return (in.readByte() & 0xff);
    29. }
    30. public static byte buffer_read_u8_byte(DataInputStream in) throws IOException{
    31. return (byte) (in.readByte() & 0xff);
    32. }
    33. public static int buffer_read_u16(DataInputStream in) throws IOException{
    34. byte[] b = new byte[]{buffer_read_u8_byte(in),buffer_read_u8_byte(in)};
    35. ByteBuffer buf = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
    36. return buf.getShort()& 0xFFFF;
    37. }
    38. public static int buffer_read_s16(DataInputStream in) throws IOException{
    39. byte[] b = new byte[]{in.readByte(),in.readByte()};
    40. ByteBuffer buf = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
    41. return buf.getShort();
    42. }
    43. /**
    44. * Skip the UDP header which the GM client included in each packet
    45. * (was nessecary due to a bug which is now fixed.)
    46. * @param input
    47. * @throws IOException
    48. */
    49. public static void skipHeaderUDP(DataInputStream input) throws IOException{
    50. //input.skipBytes(12);
    51. }
    52. public static int buffer_read_u32(DataInputStream in) throws IOException{
    53. byte[] b = new byte[]{buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in)};
    54. ByteBuffer buf = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
    55. return buf.getInt();
    56. }
    57. public static int buffer_read_s32(DataInputStream in) throws IOException{
    58. byte[] b = new byte[]{in.readByte(),in.readByte(),in.readByte(),in.readByte()};
    59. ByteBuffer buf = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
    60. return buf.getInt();
    61. }
    62. public static float buffer_read_f32(DataInputStream in) throws IOException{
    63. byte[] b;
    64. b = new byte[]{buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in)};
    65. ByteBuffer buf = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
    66. return buf.getFloat();
    67. }
    68. public static double buffer_read_f64(DataInputStream in) throws IOException{
    69. byte[] b;
    70. b = new byte[]{buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in),buffer_read_u8_byte(in)};
    71. ByteBuffer buf = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
    72. return buf.getDouble();
    73. }
    74. public static void buffer_write_u32(DataOutputStream out,int i) throws IOException{
    75. ByteBuffer b = ByteBuffer.allocate(4);
    76. b.order(ByteOrder.LITTLE_ENDIAN);
    77. b.putInt(i);
    78. out.write(b.array());
    79. }
    80. public static void buffer_write_f32(DataOutputStream out,float i) throws IOException{
    81. ByteBuffer b = ByteBuffer.allocate(4);
    82. b.order(ByteOrder.LITTLE_ENDIAN);
    83. b.putFloat(i);
    84. out.write(b.array());
    85. }
    86. public static void buffer_write_f64(DataOutputStream out,double i) throws IOException{
    87. ByteBuffer b = ByteBuffer.allocate(8);
    88. b.order(ByteOrder.LITTLE_ENDIAN);
    89. b.putDouble(i);
    90. out.write(b.array());
    91. }
    92. public static void buffer_write_u16(DataOutputStream out,int i) throws IOException{
    93. /*ByteBuffer b = ByteBuffer.allocate(2);
    94. b.order(ByteOrder.LITTLE_ENDIAN);
    95. b.putInt(i);
    96. out.write(b.array());*/
    97. out.write((byte) i);
    98. out.write((byte) (i >> 8));
    99. }
    100. public static void buffer_write_s32(DataOutputStream out,int i) throws IOException{
    101. ByteBuffer b = ByteBuffer.allocate(4);
    102. b.order(ByteOrder.LITTLE_ENDIAN);
    103. b.putInt(i);
    104. out.write(b.array());
    105. }
    106. public static void buffer_write_u8(DataOutputStream out,int i) throws IOException{
    107. out.writeByte((byte) i);
    108. }
    109. }
    Alles anzeigen


  • Sehr cool :)
    ich werde mich dann mal auf machen und deinen Code studieren. Würde es dir etwas ausmachen, wenn ich die Klasse in meinem Projekt JEN verwende?

    Was meinst du genau mit dem zusammenfügen von den Datenfragmenten?
    Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
    Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

    Willst du mit mir auf Discord Chatten/Quatschen?
    Meine Husi's Tutorial Reihe
  • Husi012 schrieb:

    Sehr cool :)
    ich werde mich dann mal auf machen und deinen Code studieren. Würde es dir etwas ausmachen, wenn ich die Klasse in meinem Projekt JEN verwende?

    Habe nichts dagegen. ^^

    Husi012 schrieb:


    Was meinst du genau mit dem zusammenfügen von den Datenfragmenten?

    Es gibt 2 Arten wie du daten über das netzwerk versenden kannst: TCP und UDP.
    UDP versendet datenpackete als ganzes. Die kommen entweder beim client an oder eben auch nicht.
    TCP hingegen baut einen "Stream" auf über den die Daten versendet werden. Wenn du über diesen Stream z.B: 20 bytes auf einmal sendest (vom server aus) kann es passieren dass diese Daten nur stückelweise (in bruchteilen) am client nacheinander ankommen. (z.B: erst die ersten 5 bytes und nach einer sekunde die nächsten 15)

    Das Problem an der Sache ist dass jedes TCP fragment das Asynchrone networking event vom GM aufruft. (Wenn du die "Raw" sockets verwendest die du für die verbindung mit einem Java server brauchst.)
    Du kannst also nicht beim aufruf des Networking Events davon ausgehen dass bereits die volle TCP nachricht beim client aufgerufen wurde.
    Wenn du z.B: sowas machst: (im Asynchronen event. Pseudocde beim empfang einer Nachricht vom Server:)

    GML-Quellcode

    1. buffer_read(tcp_buffer,buffer_f32);//ok
    2. buffer_read(tcp_buffer,buffer_f32);//ok
    3. buffer_read(tcp_buffer,buffer_f32);//ok
    4. buffer_read(tcp_buffer,buffer_f32);//CRASH! > Volle nachricht noch nicht erhalten (du liest bereits außerhalb des empfangenen Buffers)
    5. buffer_read(tcp_buffer,buffer_f32);
    6. buffer_read(tcp_buffer,buffer_f32);

    kann es (wie bereits oben gezeigt) "durch zufall" zu crashes kommen, da alle Bytes (du du zu lesen versuchst) noch nicht am Client angekommen sind.

    Der GM (sofern du einen GM basierten server verwendest) übernimmt die Arbeit für dich indem er (im Background ohne einfluss des Programmierers) zusätzliche Header informationen bei jedem TCP "packet" mitschickt.
    Der Client bzw. GM server warten dann bis alle Fragmente empfangen wurden. Erst dann wird das Asynchrone Networking Event abgefeuert.

    Du musst also im GM client die Nachrichten erst alle temporär speichern (bei jedem asynchronen networking event die ankommenden daten in einen separaten buffer oder Queue speichern) und erst dann die Nachricht verarbeiten sobald alle fragmente angekommen sind.

    Auf der Java Seite musst du nichts dergleichen machen was das ganze wesentlich simpler macht. (Durch multithreading und thread-stalling verläuft das empfangen von nachrichten nach einem anderen Prinzip.)

    Ich hatte damit damals auch zu kämpfen (dachte das wäre ein GM Bug) und habe einen thread auf der (alten) GMC erstellt wo das problem im Detail beschrieben wurde. (Mitsamt von Lösungsansätzen)
    gmc.yoyogames.com/index.php?showtopic=650252

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von LEWA ()

  • Okay danke für die Erklärung :)
    Ich schätze, ich werde es so machen, dass ich ein paar Funktionen für JEN in GameMaker mache, dass man evtl. einfach eine Funktion zu Anfangs des Events aufrufen muss um dieses zu beenden, wenn erforderlich :)
    Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
    Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

    Willst du mit mir auf Discord Chatten/Quatschen?
    Meine Husi's Tutorial Reihe
  • Hmm... ich habe jetzt etwas herumgetestet (hoffentlich liegt es nicht an meinen schlechten Javakenntnissen)
    Ich habe zwei Buffer versendet:
    erster:

    GML-Quellcode

    1. buffer_write(buff, buffer_u8, 1);
    2. buffer_write(buff, buffer_u8, 5);
    3. buffer_write(buff, buffer_u8, 10);
    4. buffer_write(buff, buffer_u8, 3);

    zweiter:

    GML-Quellcode

    1. buffer_write(buff, buffer_u8, 1);
    2. buffer_write(buff, buffer_u8, 5);
    3. buffer_write(buff, buffer_u8, 10);
    4. buffer_write(buff, buffer_u8, 7);

    In Java habe ich das ausgeben lassen:

    Quellcode

    1. for(int i=0;r.available()>0; i++)
    2. System.out.print(LewaBuffer.buffer_read_u8(r)+",");

    r ist hierbei der DataInputStream
    Die Ausgabe:

    Quellcode

    1. 222,192,173,222,12,0,0,0,4,0,0,0,1,0,5,0,
    2. 222,192,173,222,12,0,0,0,4,0,0,0,1,0,5,0,


    So richtig nach dem Inhalt des Buffers sieht es mir hierbei nicht aus :|
    Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
    Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

    Willst du mit mir auf Discord Chatten/Quatschen?
    Meine Husi's Tutorial Reihe
  • Ich hoffe mein Code ist nicht zu unaufgeräumt ^^
    Codes sind nur Teile, da beispielsweise try&catch unnötig wären, hier zu zeigen.

    Funktionen:
    Spoiler anzeigen

    Empfangen

    Quellcode

    1. InputStream is = sock.getInputStream();
    2. while(is.available()<=0){
    3. is = sock.getInputStream();
    4. }
    5. return new DataInputStream(is);

    DIS_to_String:

    Quellcode

    1. ​String str="";
    2. String line;
    3. while((line=dis.readLine())!=null)
    4. str+=line+'\n';
    5. if(str.length()>0)
    6. str=str.substring(0,str.length()-1);
    7. return str;

    CopyDIS:

    Quellcode

    1. ​byte[] save = new byte[dis.available()];
    2. for(int i=0;dis.available()>0;i++)
    3. save[i]=dis.readByte();
    4. DataInputStream[] disArr = new DataInputStream[2];
    5. disArr[0] = new DataInputStream(new ByteArrayInputStream(save));
    6. disArr[1] = new DataInputStream(new ByteArrayInputStream(save));
    7. return disArr;


    HauptCode:

    Quellcode

    1. ​DataInputStream[] buff = copyDIS(receive(socket)); //Array, damit es für Benutzung etwas komfortabel ist

    Nach etwas herumschiebereien über LambdaFunktionen kommt man dann zu dem Code von oben:

    Quellcode

    1. ​for(int i=0;r.available()>0; i++)
    2. System.out.print(LewaBuffer.buffer_read_u8(r)+",");


    Der Code sieht schlimm aus ich weiß ^^
    Keine Sorge, ich werde alles noch aufräumen :)
    Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
    Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

    Willst du mit mir auf Discord Chatten/Quatschen?
    Meine Husi's Tutorial Reihe
  • Auf der java seite sehe ich ehrlich gesagt keine Fehler.
    Was eigenartig ist dass du deutlich mehr bytes auf der Java seite empfängst als dass du sie in den Buffer schreibst.

    Wie genau erstellst du den byte buffer auf der GM seite (welchen du dann zum sever sendest)? Verwendest du zufälligerweise ein byte-alignment welches größer als 1 ist?
  • Oh stimmt, daran hatte ich nicht gedacht, ja das alignment ist auf 2. ^^
    Ich weiß nicht, wie das BufferSystem genau funktioniert. Darf man nur dieses nur unter 2 haben?
    Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
    Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

    Willst du mit mir auf Discord Chatten/Quatschen?
    Meine Husi's Tutorial Reihe
  • Husi012 schrieb:

    Oh stimmt, daran hatte ich nicht gedacht, ja das alignment ist auf 2. ^^
    Ich weiß nicht, wie das BufferSystem genau funktioniert. Darf man nur dieses nur unter 2 haben?

    Ich bin mir nicht 100%ig sicher wie genau bytes bei einem byte-alignment von > 1 gespeichert werden, daher könnte meine Aussage nicht akkurat sein.
    Mit dem Byte alignment kannst du sicher stellen dass jegliche information in fixen "byte-schritten" gespeichert wird.
    Wenn du also z.B: ein byte alignment von 2 hast und du nur 1 byte in den Buffer schreibst, so wird der eine byte in den buffer geschrieben und die aktuelle schreibposition im Buffer springt auf die nächst zugängliche 2-er stelle im buffer. (Allerdings könnte ich da auch komplett falsch liegen da ich das noch nie verwendet habe.)


    Due Grundregel heißt also: Stelle das Byte-alignment auf 1. (Dadurch kommst du beim aufbauen eines eigenen Binären formats nicht mit der byteposition durcheinander da nirgendwo irgendwelche ungewünschten lücken auftauchen können.) Ich habe noch nie alignment werte > 1 verwendet.

    Ausserdem:
    Stelle sicher dass (wenn du den buffer versendest und dieser auf buffer_grow gestellt wurde) du bei der anzahl der bytes (die vom buffer versendet werden sollen) die aktuelle lese/schreib position im Buffer verwendest und nicht die größe des buffers:

    GML-Quellcode

    1. network_send_raw(connection, tcp_out_buffer, buffer_tell(tcp_out_buffer));

    Das liegt daran dass bei buffer_grow der buffer zwar beim überschreiten der aktuellen größe vergrößert wird, das passiert aber (ich glaube zumindest) exponentiell wodurch am ende ungewollt mehrere unbenutzte bytes mitgesendet werden könnten.

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von LEWA ()

  • Ahh okay danke.
    Also macht das alignment Lücken, wenn erwünscht :) . So habe ich das in der GM Hilfe nicht deuten können ^^

    LEWA schrieb:

    Ausserdem:
    Stelle sicher dass (wenn du den buffer versendest und dieser auf buffer_grow gestellt wurde) du bei der anzahl der bytes (die vom buffer versendet werden sollen) die aktuelle lese/schreib position im Buffer verwendest und nicht die größe des buffers:

    jup das wuste ich schon :)

    Jetzt kommt das hier an:

    Quellcode

    1. ​222,192,173,222,12,0,0,0,4,0,0,0,1,5,10,3,
    2. 222,192,173,222,12,0,0,0,4,0,0,0,1,5,10,7,

    die letzten 4 Werte passen. Wie ich aus dem Thread des alten GMC Forums entnehmen konnte, sind dann auch die 12 Bytes davor der normale Header, welchen GM mit sendet.
    Ist es möglich, die 12Bytes auch weg zu lassen?
    Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
    Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

    Willst du mit mir auf Discord Chatten/Quatschen?
    Meine Husi's Tutorial Reihe
  • Husi012 schrieb:


    Jetzt kommt das hier an:

    Quellcode

    1. ​222,192,173,222,12,0,0,0,4,0,0,0,1,5,10,3,
    2. 222,192,173,222,12,0,0,0,4,0,0,0,1,5,10,7,

    die letzten 4 Werte passen. Wie ich aus dem Thread des alten GMC Forums entnehmen konnte, sind dann auch die 12 Bytes davor der normale Header, welchen GM mit sendet.
    Ist es möglich, die 12Bytes auch weg zu lassen?

    Verwende die _raw varianten der networking funktionen. (network_connect_raw(), network_send_raw(), etc...).
    Wenn du die default networking funktionen verwendest (die ohne _raw) wird der GM header mitgesendet, da diese darauf ausgelegt sind mit einem GM server zu kommunizieren.
  • ich habe connect_raw verwendet, die send_packet_raw nicht gefunden.
    Muss ich jetzt auf udp zurückgreifen, weil es nur network_send_udp_raw gibt?
    edit: hab die send_raw voll übersehen. Perfekt danke :)
    Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
    Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

    Willst du mit mir auf Discord Chatten/Quatschen?
    Meine Husi's Tutorial Reihe
  • *Thema neu aufgreifen*

    LEWA schrieb:

    Auf der Java Seite musst du nichts dergleichen machen was das ganze wesentlich simpler macht. (Durch multithreading und thread-stalling verläuft das empfangen von nachrichten nach einem anderen Prinzip.)

    @LEWA ist es ganz sicher, dass theoretisch durch einer sehr sehr schlechten Verbindung in Java trotzdem alles vollständig ankommt?
    Ich würde mir sonst so ein "warte ding" machen, was dann erst auf alles wartet.
    Bis jetzt konnte ich nur feststellen, das zwei Pakete, die ich nacheinander sende als eins angesehen werden.
    Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
    Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

    Willst du mit mir auf Discord Chatten/Quatschen?
    Meine Husi's Tutorial Reihe
  • Husi012 schrieb:

    *Thema neu aufgreifen*

    LEWA schrieb:

    Auf der Java Seite musst du nichts dergleichen machen was das ganze wesentlich simpler macht. (Durch multithreading und thread-stalling verläuft das empfangen von nachrichten nach einem anderen Prinzip.)

    @LEWA ist es ganz sicher, dass theoretisch durch einer sehr sehr schlechten Verbindung in Java trotzdem alles vollständig ankommt?
    Ich würde mir sonst so ein "warte ding" machen, was dann erst auf alles wartet.
    Bis jetzt konnte ich nur feststellen, das zwei Pakete, die ich nacheinander sende als eins angesehen werden.


    TCP stellt sicher dass das Paket ankommt. Im Gegensatz zu UDP fragt TCP jedesmal beim anderen Teilnehmer nach ob das packet auch wirklich angekommen ist. Falls nicht, wird dieses neu gesendet. (Dadurch hat TCP generell einen höheren overhead beim netzwerkverkehr was das ganze "langsamer" als UDP macht.)
    Falls du also eine sehr schlechte verbindung haben solltest (mit hohem packet-loss) kann es sein dass TCP ein packet mehrere male verschicken muss damit es beim client ankommt (was dauern kann, aber es kommt an sofern es die verbindung zulässt). Falls die Verbindung katastrophal schlecht sein sollte kann es passieren dass du einfach einen Timeout beim verschicken des packetes bekommst.

    Bis jetzt konnte ich nur feststellen, das zwei Pakete, die ich nacheinander sende als eins angesehen werden.

    Was ich empfehlen würde ist den Nagle Algorithmus bei TCP zu deaktivieren. Dieser versucht mehrere packete die versendet werden zu einem größeren zusammenzufassen um den Traffic zu schonen (weniger header daten die versendet werden müssen.) Mir war das jedoch im wege da ich oftmals kleinere TCP nachrichten versende die sofort beim client ankommen müssen und nicht sobald ich erst weitere 2-3 packete absende.
    Der GM verwendet den Algorithmus auch nicht.

    Auf der Java seite kannst du den so deaktivieren:

    Quellcode

    1. Socket newConnection;
    2. newConnection = server.getServerSocketTCP().accept();//new connection
    3. newConnection.setTcpNoDelay(true);//Deactivate Nagles algorithm

    Damit werden packete sofort versendet wenn du den befehl gibst und nicht willkürlich zu einem zusammengesetzt.