Mosaik Synchronisation und z-Index

Saturday, 15. April, 2006

Da BunteSuppe eine Multiplayer, fast Echtzeit Application ist, müssen wir die Spieler miteinander und mit dem Server synchronisieren, so dass jeder Spieler exakt dasselbe sieht in seinem Browser.

z index

Der schwierige Part dieses komplexen Problems entsteht durch den z-Index. Das ist ein Wert, der die Reihenfolge überlappender Elemente auf einer Seite definiert. Im Fall der Mosaike auf BunteSuppe definiert der z-Index, welche Steinchen über oder unter anderen Steinchen sind.

Als Menschen erwarten wir instinktiv, dass wenn wir ein Steinchen ziehen, dieses per Definition über all den anderen Steinchen ist und wenn wir es irgendwo hin bewegen, das bewegte Steinchen also auf den anderen Steinchen oben drauf landet, d.h. es hat den höchsten z-Index.

Der z-Index

Der z-Index ist technisch gesehen eine Eigenschaft des Styles eines Objekts. Der z-Index eines Objekts wird mit folgender Syntax geschrieben:

Beachte, dass wenn wir den z-Index eines Objekts mittels CSS definieren, schreibt man ihn als “z-index” (mit Bindestrich):

<style type="text/css">
#mydiv {z-index: 10;}
</style>

Wenn man diese Eigenschaft mittles JavaScript bearbeitet, dann lautet die Syntax “zindex”. Das Style-Objekt ist ein Child-Objekt eines Objekts.

<script type="text/javascript">
mydiv=document.getElementById('mydiv');
mydiv.style.zIndex=10;
</script>

Welches Objekt auch immer den höchsten z-Index hat, erscheint als Oberstes der Objekte. Der tatsächliche Wert ist nicht wichtig – die Elemente werden vom Browser einfach nach dem z-Index eines jeden Elements erstellt.

Das Element mit dem kleinsten z-Index wird zuerst gezogen und erscheint ganz unten, das Objekt mit dem höchsten z-Index wird als letztes gezogen und erscheint an oberster Stelle, also auf allen anderen Objekten oben drauf.

Synchronisation des z-Index für alle Spieler

Unser Ziel war es, sicherzustellen, dass egal wieviele Spieler aktiv sind, jeder genau dasselbe Mosaik sieht und alle Steinchen in der exakt selben Reihenfolge überlappen.

Der Trick dabei ist es, einen Algorithmus zu entwickeln, der die Reihenfolge beibehält, in der die Steinchen bewegt worden sind, weil diese Reihenfolge den z-Index eines jeden Steinchens bestimmt – was als letztes bewegt worden ist, soll ganz oben sein.

Unser Algorithmus dafür geht wie folgt.

Auf BunteSuppe kann es mehr als ein aktives Mosaik geben, mit verschiedenen Steinchenpositionen und verschiedenen Spielern. Für jedes Mosaik gibt es einen “Move Counter” (Bewegungszähler), der inkrementiert, wenn ein Steinchen bewegt wird. Hinsichtlich Database-Strukturen bezeichnen wir jedes Mosaik als ein “Set”. Daher ist der Move Counter in einem “Set Table” gespeichert und wir sprechen von ihm als “Set Move Counter”.

Die neuen Steinchenpositionen auf den Server schreiben

Wenn ein Spieler ein Steinchen zieht, wird die neue Position des Steinchens auf den Server geschrieben. Alle Daten eines jeden Steinchens werden in einem “Slot” in einem Slot Table gespeichert. Jeder Slot speichert die x/y Position des Steinchens, die Move Number (Bewegungs-Nummer) des Steinchens, die Steinchenfarbe und die ID des Spielers, der als Letzter das Steinchen bewegt hat.

Das Server Script muss nun die Move Number eines jeden Slots updaten, der zum Updaten ansteht. Das wird via Ajax aufgerufen – ein Write Request wird an den Server gesendet, der die Positionen der bewegten Steinchen beinhaltet.

Es ist möglich, dass man die Steinchen so schnell bewegt, dass jeder Write Request mehrere Slots updatet.

Der erste dieser Slots, der upgedatet wird, bekommt einen Move Number von “Set Move Counter + 1″, der nächste bekommt “Set Move Counter + 2″, usw.

Um dies zu erreichen, sperrt das Script zuerst den Table mit dem Move Counter (den Set Table), um zu verhindern, dass andere Spieler diesen Wert zur gleichen Zeit verändern.

Darauf wird die derzeitige Move Number gelesen und mit der Move Number des letzten Slots, der upgedatet werden soll, upgedatet. In anderen Worten, wenn wir 5 Slots updaten müssen, würde die Update Query ein “Set Move Number = Set Move Number + 5″ ausführen.

Der Table wird dann wieder entsperrt.

Auslesen der neuen Steinchenpositionen für Steinchen, die sich seit dem letzten Update bewegt haben

Dasselbe Server Script, dass die Write Operation ausführt, sendet auch an den Browser der Spielers die Daten, welche Steinchen seit dem letzten Update bewegt worden sind.

Also, wie wissen wir jetzt, welche Steinchen zum Browser gesendet worden sind?

Aus dem Slot Table wählt das Script all jene Steinchen aus, die eine höhere Move Number haben als die höchste Move Number des letzten Updates für diesen Spieler, und sendet diese Information an den Browser, aufsteigend sortiert nach der Move Number.

Zuletzt speichert der Server die höchste Move Number aller Steinchenpositionen, die zum Browser im letzten Update gesendet worden sind. Diese “höchste Move Number” wird dann seperat für jeden Spieler gespeichert und zwar in einem Table, den wir “Room Occupants Table” (Room Nutzer Table) nennen.

Client-seitig: Überwachung des z-Index

Wenn das Ajax Objekt des Browser die Update Information des Servers erhält, wird es – in der Reihenfolge der gesendeten Positionsinformationen – Folgendes bei jedem der Steinchen machen:

  • Den globalen z-Index inkrementieren;
  • Den z-Index des Steinchens auf diesen Wert setzen.

Jetzt, was ist mit dem Steinchen, das gerade bewegt wird? Wird es nicht immer ganz als oberstes Steinchen gelegt? Naja, ja am Anfang ist es so. Aber dann, wenn die Position des Steinchens zum Server gesendet wurde, wird es in die zeitliche Reihenfolge mit all den anderen Spielern gebracht.

Daher wird auch Deine eigene Steinchenposition zurück zum Browser gesendet und ist somit brav in der Reihenfolge mit all den anderen Steinchen, die gleichzeitig bewegt worden sind.

Voila! Jeder Spieler sieht die Steinchen auf die exakt selbe Art und Weise überlappen.