Naja .. ich frage alles in 1 Query ab. Da kann die Datenbank optimieren, jedes Feld wird max 1x geprüft und durch indizes und bereichs-einschränkungen hab ich so nur noch eine gerine anzahl an potentiellen vergleichen ;)
Mal ein Beispiel, wie ich das umgesetzt hab - ist mittlerweile schon optimiert und fragt in echtzeit ab, welches von ca. 30.000 Objekten sich gerade in reichweite eines planeten (flotten haben das atm nicht, die künnte man auch noch hinzufügen - am besten ohne union ;) ) befindet.
PHP:
static function getObjectsRectangle($uid,$x_from,$y_from,$x_to,$y_to) {
/*
* where
* (sundiff² < range² 400 and (sundiff² < range² -400) or [exact calc])) or stationrange and [...]
*/
$pdo =& api::getPDO();
$objekte = array();
$qry = $pdo->query(
'select DISTINCT o.oid, ' .
'o.x,' .
'o.y,' .
'obr.name_'.$_SESSION['lang'].' as typ,' .
'o.met, ' .
'o.sil, ' .
'o.verb, ' .
'o.krist, ' .
'o.kristverb, ' .
'o.energ, ' .
'o.zit' .
' from ' .
'objekte o join objektreferenz obr on (o.otyp = obr.otyp), ' .
'gebaeude g join gebaeudereferenz gr on (g.gid = gr.gid) left outer join stationen st on (st.stationid=g.stationid and st.uid='.$uid.') left outer join planeten p on (g.pid=p.pid and p.uid='.$uid.') left outer join sonnen s on (s.sid=p.sid)' .
' where ' .
'('.
'(pow(o.x-s.x,2) pow(o.y-s.y,2) ' .
'< case g.gid ' .
'when 50 then pow(500 - 500*pow(0.75,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 51 then pow(1000 - 1000*pow(0.8,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 52 then pow(750 - 750*pow(0.90,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 53 then pow(1250 - 1250*pow(0.95,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'ELSE 0 end 400 AND ' .
'(pow(o.x-s.x,2) pow(o.y-s.y,2) ' .
'< case g.gid ' .
'when 50 then pow(500 - 500*pow(0.75,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 51 then pow(1000 - 1000*pow(0.8,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 52 then pow(750 - 750*pow(0.90,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 53 then pow(1250 - 1250*pow(0.95,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'ELSE 0 end - 400 ' .
'or ' .
'pow(o.x-s.x-(p.pnr*cos(unix_timestamp()*p.bahnfaktor/(p.pnr*13750.987083139757010431557155385))),2) pow(o.y-s.y-(p.pnr*sin(unix_timestamp()*p.bahnfaktor/(p.pnr*13750.987083139757010431557155385))),2) ' .
'< case g.gid ' .
'when 50 then pow(500 - 500*pow(0.75,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 51 then pow(1000 - 1000*pow(0.8,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 52 then pow(750 - 750*pow(0.90,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 53 then pow(1250 - 1250*pow(0.95,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'ELSE 0 end ))' .
'or pow(o.x-st.x,2) pow(o.y-st.y,2) ' .
'< case g.gid ' .
'when 56 then pow(500 - 500*pow(0.75,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 57 then pow(1000 - 1000*pow(0.8,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 58 then pow(1500 - 1500*pow(0.90,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) ' .
'when 59 then pow(2000 - 2000*pow(0.85,g.stufe),2)*g.prodfaktor*g.arbeiter/(gr.arbeiter*g.stufe) '.
'ELSE 0 end) AND ' .
'g.gid in (50,51,52,53,56,57,58,59) AND ' .
'g.uid = '.$uid.' AND ' .
'o.x between '.($x_from).' and '.($x_to).' AND ' .
'o.y between '.($y_from).' and '.($y_to));
while ($row = $qry->fetch(PDO::FETCH_ASSOC)) {
array_push($objekte,$row);
}
$qry->closeCursor();
return $objekte;
}
Die Berechnung sieht etwas eklig aus, da während der qry die positionen der Planeten erst noch errechnet werden (sonnenposition radius*umlaufgeschwindigkeit).
Vom Prinzip her baut das ding 2 temporäre Tabellen. Zum einen die Objekte-Tabelle eingeschränkt auf die koordinaten (xfrom, xto, yfrom, yto). Zum anderen eine Tabelle aller Dinge die sehen können mit prinzipiell folgenden angaben: xpos, ypos, reichweite.
Und dann kommt nur noch nen bissl pythagoras im where und das wars. Um das ganze zu optimieren (insbesondere bei den planeten, weil ich da sin/cos-berechnungen drin hab, die teuer sind) werden bei den bewegenden planeten noch 2 zusätzliche bereichsprüfungen fällig. Zum einen das "definitiv drin", sprich wenn der planet selbst in ungünstigster stellung noch das objekt sehen kann (unabhängig, wie er grad steht; kann man mit 2 pyth. abschätzen) und zum anderen das "definitiv draußen", halt wenn man ihn nicht sehen kann.
Durch die anordnung (CheckDraussen AND (CheckDrin OR RechneGenau)) wird das where an der frühestmöglichen stelle abgewürgt. Ist CheckDraussen false, ist der ausdruck false und wird nicht weiter ausgewertet; ist es true und CheckDrin true ist der ausdruck auch true und ich hab die sin/cos-krams nicht. Und wenn alles fehlschägt (also selten), dann rechnet er genau.
Das ganze ding läuft mit 100 einträgen in der theoretischen "sichbarkeitstabelle" (die mit x,y,radius) und 30k objekten in der "zu überprüfen"-tabelle ganz zackig durch... insbesondere, wenn man die gelesenen Tabellen im Ram hält, weil sie ohnehin fast nur read-only sind.
Das sind meine erfahrungen dazu.. und ich weiss - es ist übertrieben alles sooo haarklein live zu berechnen .. mit caching und allem drum und dran könnte man tonnen von performance sparen ... ;)
Für andere einsatzzwecke gibt es sicher auch noch andere herangehensweisen.
Man kann z.b. für jeden Spielen eine "hat er schon gesehen"-Map erzeugen, die auf Rechteckrasterung basiert und sich in einem Quadtree sowohl im Ram, wie auch serialisiert auf der Platte halten kann. In der DB würd ich das nicht ablegen, da die Daten ohnehin meist statisch sind (bzw. man die Momente in denen sich was z.b. durch einen gebbau ändert abfangen kann und dann "manuell" den Eintrag updatet) und keine hohen anforderungen an durchsuchbarkeit etc haben.
Edit: BTW: Die Query losgelassen auf alle 30k Objekte (und nicht nur die Sektoren, die angefragt wurden) dauert um die 800ms. Da kann man mit bereichssuchen auch noch einschränken, ist für meinen Fall aber nicht notwendig, da ich immer nur ein paar sektoren per ajax nachliefer ;)
Ein konkreter Ausschnitt (Sektoren sind bei mir 500x500 einheiten gross, können aber aneinander gehängt werden und so größere "kästen" bilden) von 2000x1500 Einheiten dauert nur um die 50ms. Mein Spieluniversum hat lediglich die Ausdehnung von 10k x 10k Einheiten, also sind noch größere anfragen eher selten.
Wie sich die performance aber weiter auswirkt (wenn man mehr als 10-20 planeten/stationen hat oder wenn ich die Flotten etc. noch mit in dieselbe qry nehme), hab ich noch nicht getestet.