Die Planung meiner neuen OpenSim Software
GridNode – Features (Rust + Rapier vs. OpenSim + ubODE)
Dieses Dokument beschreibt die Fähigkeiten der neuen Grid-Software gridnode gegenüber dem klassischen OpenSim/Robust (Mono/C# + ubODE-Physik). GridNode ist eine neu entwickelte Grid-Server-Software – kein Fork, keine Herauslösung einzelner Robust-Handler, sondern eine eigenständige Software in Rust (native Binary, kein GC/JIT, planbarer RAM, speichersicher, Cross-Plattform Linux + Windows ohne Runtime beim Nutzer; Ausnahme: reine CPU-Hotspots wie Kompression dürfen in C eingebunden werden).
Nach außen bleibt GridNode strikt LLUDP + CAPs + SL-Asset-Typen, damit Firestorm, Alchemy, Cool VL ohne einen einzigen Patch verbinden. Nach innen ist alles neu, modern, parallel und schnell. Dieses Dokument geht ausschließlich um die Fähigkeiten selbst – nicht um Installation (siehe installation.md) und nicht um die Bauwellen-Reihenfolge/Roadmap (siehe roadmap.md).
Produktziel in einem Satz: Betritt ein User mit unverändertem Firestorm ein gridnode-Grid, soll er innerhalb der ersten Minute merken, dass alles rezt in Sekunden statt Minuten, flüssig läuft, PBR-fotorealistisch aussieht, saubere Physik hat, Sub-100-ms-Chat/Teleport reagiert und spatial WebRTC-Voice funktioniert – ohne dass er irgendetwas umgestellt hat.
1. Physik: Rapier statt ubODE
Rapier-Physik (nativ Rust, Multi-Core) als ubODE-Ersatz. Deterministisch, saubere Fahrzeuge/Boote/Fluggeräte.
Vehicle-Constraints (llSetVehicleType-kompatibel): Ein Test-Fahrzeug fährt sauber und fällt nicht durch den Boden – dort, wo ubODE bei Fahrzeugen bekannte Schwächen hat, muss Rapier messbar sauberer sein.
Determinismus: Rapier läuft mit enhanced-determinism – dieselbe fixierte Feature-Matrix bildet zusammen mit dem WASM-Fuel-Determinismus die Basis für späteren Rollback-Netcode.
Physik-Schutz / Anti-Griefing (§17a.4):
Cap auf dynamische Bodies pro Avatar (MaxDynamicBodiesPerAgent, Default
– verhindert das Fluten des IslandManager durch „1000 Trümmer am Körper".
Auto-Sleep für ruhende Bodies (Standard) – ruhende Masse kostet keine CPU.
CCD bleibt an (schnelle Objekte tunneln nicht), ist aber pro Region budgetiert.
Absurd hohe Kollisions-Shapes werden auf Convex-Hull/Box degradiert (Rapier SharedShape), statt die Broad-Phase zu fluten.
Der Kernunterschied zum Alten: Klassisches OpenSim fährt die Region im Single-Thread + .NET-GC – eine überkomplexe Physiklast reißt die ganze Sim mit. Rapier läuft parallel (siehe §2) und ist pro Region konfigurierbar budgetiert.
2. Multi-Core / Nebenläufigkeit (die VM darf, was sie kann)
Grundregel (nicht verhandelbar): Was die VM/der LXC an CPU-Kernen und RAM hat, darf gridnode auch benutzen. Kein künstliches Single-Thread-Limit wie im klassischen Mono-OpenSim.
tokio Multi-Thread-Runtime überall (worker_threads = num_cpus). Kein current_thread-Runtime für Server-Prozesse.
CPU-heavy Arbeit (Physik-Steps, KTX2-Transcode, JPEG2000-Decode, Hashing, LSL→WASM-Kompilierung, Interest-Management-Rechnungen) läuft im rayon-Pool über alle Kerne, nicht im Async-Runtime.
LLUDP-Paketverarbeitung über einen Worker-Pool (mehrere Tasks pro Circuit), damit ein zappelnder Client keinen anderen ausbremst.
WASM-Scripting (wasmtime): Instance-Pool + eigener Thread-Pool, jedes Skript mit eigenem Zeit-Budget, keine globale Sperre.
HTTP/2-CAPs (hyper): multiplexed, laufen auf allen Tokio-Workern.
DB-Zugriffe: sqlx-Pool mit max_connections = 2 × num_cpus als Startwert.
Ressourcen-Freigabe: Die [Runtime]-Section darf Limits hochsetzen, nicht künstlich runter (WorkerThreads/RayonThreads/WasmThreads = auto). gridnode-region/-login/-caps bekommen keine CPUQuota (dürfen 100 % aller Kerne nutzen); nur Worker (IAR/OAR/Backup/HG/Cleanup) sind gequotet.
Verifikation: Unter Last muss htop/top -H zeigen, dass alle Cores der VM ausgelastet werden, nicht nur einer; Prometheus-Metriken exportieren per-Thread- CPU-Zeit, damit Single-Thread-Regressionen sofort auffallen.
Ökosystem: async I/O über tokio, HTTP über hyper/axum.
3. Prozess-Architektur (jeder Handler ein eigener Prozess)
Problem beim Alten: Klassisches OpenSim/Robust läuft im Wesentlichen in einem Sim- und einem Robust-Prozess. Sobald jemand eine große IAR/OAR importiert oder exportiert, ein Backup läuft oder ein HG-Transfer viele Assets zieht, geht der ganze Prozess (und damit die Region bzw. das Grid) in die Knie – Avatare ruckeln, Login geht nicht mehr, Physik steht.
Regel bei GridNode (nicht verhandelbar):
Jeder Handler = eigener OS-Prozess, verwaltet über eine eigene systemd-Unit (Restart=on-failure, eigene Ressourcen-Limits via CPUQuota=, MemoryMax=, IOWeight=).
Kein Handler darf die Simulation blockieren. Lange laufende Arbeit (Import/Export/Backup/HG-Bulk) läuft immer in einem Worker-Prozess mit eigener CPU-/IO-/Memory-Quote.
Kommunikation ausschließlich über IPC (Unix-Domain-Sockets + gRPC oder tokio-Message-Bus). Kein Shared-State im Speicher zwischen Handlern.
Backpressure ist Pflicht: Jede IPC-Queue hat eine feste Kapazität; ist sie voll, wird der Producer gebremst – niemals der Sim.
Priorität: gridnode-region und gridnode-login höchste Priorität (Nice=-5); Worker (gridnode-iar/-oar/-backup/-hg) niedrige (Nice=+10, IOWeight=50).
Crash-Isolation: Stürzt ein Worker ab, startet ihn systemd neu; Region und Login laufen ungestört weiter.
Prozess-Landkarte (systemd-Units): gridnode-region (LLUDP, Physik, Interest-Management, EventQueue), -login, -caps (HTTP/2-CAPs), -assets (CAS-Storage, KTX2-Transcode-Queue, Cache), -peer (Peer-Hub, Gossip/PEX, Heartbeats), -voice (WebRTC-SFU-Bridge), -scripts (WASM-Runtime), -jobs (persistente Job-Queue), -iar, -oar, -backup, -hg, -metrics, -search, -webviewer.
Effekt („Ei-aus-der-Hose"): Region-TPS bleibt selbst während 5-GB-OAR-Exports stabil; Login und Teleport funktionieren, während ein IAR importiert wird; ein defekter/hängender Worker reißt nichts anderes mit; Ressourcen sind planbar.
Kill-Switch: Owner/Admin kann jeden Job per Admin-CAP abbrechen – das killt nur den Worker-Prozess, nicht die Region.
3.1 Admin-Konsole (gridnode console)
Ersatz für die interaktive OpenSim/Robust-Konsole: ein REPL mit kurzen, sprechenden Befehlen, der Config + gridnode.env selbst lädt.
Lese-Befehle: show regions, show users, show estates, show services (gridnode-*-Units + Status), help, quit/exit.
Schreib-Befehle (Anlegen): create user, create inventory (Backfill des Standard-Ordner-Skeletts), create estate, create region (mit Grundflächen- Kollisionsprüfung über die volle Region-Fläche, size = positives Vielfaches von 256).
Schreib-Befehle (Ändern): change region estate, change estate owner.
Als Dienst attachbar (wie Robust): gridnode-console.service startet die Konsole in einer screen-Session (screen -r gridnode-console, lösen mit Ctrl-a d); quit/exit schließt nur den Prompt und öffnet sofort einen frischen. PATH-Symlink /usr/local/bin/gridnode.
4. Asset-System: CAS/SHA-256 statt BLOB-Chaos
Problem beim Alten: In klassischem OpenSim liegen Assets als BLOBs in der DB (assets-Tabelle), verknüpft über zig Tabellen (inventoryitems, inventoryfolders, prims, primitems, terrain, regionsettings, avatarappearance, bakedtextures, …). Ein „welche Assets braucht niemand mehr?" ist praktisch nicht beantwortbar → DBs wachsen ins Unendliche, Backups werden riesig, Migration ist Hölle.
Lösung bei GridNode: Assets werden strikt getrennt von Metadaten gespeichert. Die DB kennt nur Referenzen, nie den Inhalt.
4.1 Content-Addressable Storage (CAS)
Pfad = Hash: cas/// (zweistufige Fanout-Struktur gegen Riesen-Verzeichnisse; ab ~5 Mio. Dateien/Node auf dreistufig migrierbar, [Assets] FanoutDepth). Datei = Roh-Asset (Textur/Mesh/Sound/Notecard/…).
Deduplication automatisch: Zwei identische Texturen = 1 Datei.
Immutable nach Landung (nicht verhandelbar): Sobald eine Datei atomar in cas/ liegt, wird sie chmod 0444 gesetzt. Der Hash bleibt damit für immer gültig; versehentliches Überschreiben ausgeschlossen. Änderungen = neues Asset mit neuem Hash.
Filesystem-Backups (rsync/zfs-snapshot) reichen – keine 200-GB-MySQL-Dumps.
Schlanke DB-Tabellen: asset_meta (sha256 PK, asset_type, mime, size, created_at, origin, origin_sha256), asset_ref (eine verbindliche Referenz-Tabelle für Cleanup), inventory_item, inventory_folder, prim, prim_asset. Jede Stelle, die früher eine asset_id (UUID) hielt, hält jetzt einen asset_sha256.
4.2 Cleanup endlich möglich (Mark-and-Sweep)
Ein trivialer Mark-and-Sweep über die Referenz-Tabelle:
Mark: gridnode-jobs durchläuft asset_ref + referenzierende Tabellen und markiert alle genutzten sha256.
Sweep: Alles Unmarkierte, das älter als X Tage ist (Standard 30), wandert nach quarantine/.
Purge: Nach weiteren Y Tagen (Standard 30) endgültige Löschung.
Läuft als eigener Worker gridnode-cleanup (systemd-Timer, nachts), belastet die Sim nicht; Owner kann per Admin-CAP „Dry-Run" fahren.
4.3 Asset-Performance & Sicherheit (rein serverseitig, Viewer merkt nichts)
Kanonisierung vor dem Hashen (Voraussetzung für echte Dedup): Notecards (UTF-8 NFC, LF), LSL-Text (UTF-8 NFC, LF), Meshes (kanonische LLSD-Binary-Serialisierung mit fester Feld-Reihenfolge), Texturen (J2K→KTX2 falls Ktx2OnImport=true). Hash immer über die ausgelieferte Form. Original-Bytes werden separat als origin_sha256 gehasht und – falls abweichend – zusätzlich abgelegt (für bit-identischen HG-Export).
Immutable-Auslieferung mit ewigem Cache: GetTexture/GetMesh/GetMesh2 tragen Cache-Control: public, immutable, max-age=31536000 und einen ETag = SHA-256. Firestorm/libcurl cached damit cross-region und cross-grid unbegrenzt → beim zweiten Besuch Rez-Zeit ~0.
Zero-Copy-Auslieferung (hyper + Linux): CAS-Dateien per sendfile(2) / tokio-uring splice direkt aus dem Filesystem-Cache in den TCP-Socket – kein Userspace-Puffer, keine Kopie. Ein gridnode-caps-Prozess sättigt trivial 1 Gbit/s bei zweistelliger CPU-Auslastung. HTTP/2 multiplexed hunderte parallele Asset-Requests eines Viewers über eine Verbindung.
Prefetch entlang der Interest-Liste: gridnode-region weiß per Interest-Management, welche Prims als nächstes sichtbar werden; gridnode-assets warmed die zugehörigen Texturen/Meshes in den OS-Filesystem-Cache vor, bevor Firestorm sie anfragt → keine grauen Kartons mehr bei Bewegung/Draw-Distance-Erhöhung.
Sichere Uploads / Peer-Fetches (Hash ist der Beweis): Jeder eingehende Byte-Strom wird streaming gehasht; kein Byte landet in cas/, bevor der SHA-256 stimmt. Atomarer Schreibvorgang tmp/.partial → fsync → rename() → chmod 0444. Fehlgeschlagene Hash-Prüfung → quarantine/. Peer-Antworten müssen nicht signiert sein – der Hash beweist den Inhalt, egal wer die Bytes liefert.
Fsync-Modus ([Assets] FsyncMode = per_file | per_batch, Default per_file): per_batch schont SSD-IOPS bei Bulk-Uploads (nur bei journal-safen FS zulässig).
4.4 Asset-Resilienz im Peer-Verbund (kein toter Link, kein grauer Karton)
Regel (nicht verhandelbar): Wenn ein Grid ein Asset einmal aus dem Peer-Verbund geladen hat, liegt es dauerhaft im lokalen CAS. Fällt das Ursprungs-Grid aus, bleibt das Asset für die eigenen Nutzer verfügbar. Der Peer-Verbund ist Cache/CDN, keine Live-Referenz nach außen.
Sobald ein Grid ein Asset per SHA-256 zieht, wird es atomar in das lokale cas/ geschrieben und ist ab dem Moment eigener Besitz. Keine URL-/Grid-Bindung mehr – kein „Asset lebt auf Grid X". asset_meta.origin hält nur zur Statistik fest, wo es herkam, nicht als Auflösungsquelle.
Optionale Replikation: [Assets] Replicas = N (Default 0, empfohlen 2–3 für Community-Grids) pusht neue eigene Assets aktiv an N weitere Peers.
Retention: Lokal gezogene Peer-Assets zählen wie eigene und werden erst vom Mark-and-Sweep angefasst, wenn sie lokal von keinem Prim/Inventar mehr referenziert werden. „Ursprungs-Grid offline" ist kein Löschgrund.
HG-Sonderfall: Assets aus klassischen HG-Fremdgrids werden beim ersten Zugriff immer kopiert und lokal persistiert.
Ablauf beim Fetch: Fehlt lokal, fragt gridnode-assets den Peer-Verbund (SHA-basiert, Anycast auf bekannte Peers, nach Latenz sortiert); Antwort wird gleichzeitig an den Viewer gestreamt und in tmp/ geschrieben, nach Verifikation atomar nach cas/ verschoben. Ein temporärer Miss löst retry/„lädt" aus, nie sofort Unable to find asset.
4.5 Asset-Redundanz ([Assets] Replication)
Asset-Verlust (grauer Avatar, fehlende Kleidung) ist der schlimmste Fehler für den User – Redundanz ist Pflicht, nicht Option. Ein einziger Regler:
Replication = k1 (nur Kapazität, kein Failover) · k2 (Standard) · k3 (maximal).
k ist ein Ziel-Zustand: Ein Reconciliation-Loop (Ist→Soll, im gridnode-jobs, gedrosselt/resumierbar) zieht bei Ist < Soll fehlende Kopien nach (Self-Healing auch bei Knoten-Ausfall) und entfernt bei Ist > Soll überzählige.
Downgrade-Safety-Gate: Eine Kopie wird erst gelöscht, wenn nachweislich k verifizierte Kopien auf gesunden Knoten existieren; nie löschen, solange ein Knoten offline ist; Grace-Period statt Sofort-Löschung.
Hot-Reload ohne Neustart: gridnode assets reload liest die INI neu ein und stößt den Reconciliation-Job an – kein Prozess-Neustart.
Erasure Coding (Reed-Solomon m+p) ist als gleichrangiges Speicher-Profil für große Pools geplant (gleiche Sicherheit bei deutlich weniger Platz).
Content-Adressierung, k-Replikation und EC sind rein intern (Data-Plane). Nach außen (Viewer, HG) sprechen wir klassisch OpenSim (UUID); die Brücke ist die legacy_asset_map.
4.6 Migration von klassischem OpenSim (kein Neubau, kein Vendor-Lock)
Umsteigende Owner exportieren ihre Alt-Welt als IAR (Inventar) und OAR (Region) und importieren sie – immer über die Worker-Prozesse gridnode-iar/gridnode-oar, damit die Region weiterläuft.
Import-Pipeline pro Asset: Kanonisierung vor Hash → SHA-256 wird cas/-Name; Original-Erhalt als origin_sha256 (bit-identische HG-Rückgabe); Dedup automatisch; Alt-UUIDs bleiben in legacy_asset_map (uuid → sha256) erhalten (HG-Referenzen weiter auflösbar); JPEG2000 optional zusätzlich als KTX2 abgelegt (ktx2_on_import=true), Original bleibt.
Kein Passwort für IAR/OAR-Befehle: Anders als klassisches OpenSim (load iar ) verlangen gridnode-Befehle kein Nutzer-Passwort; die Autorisierung ergibt sich aus dem Owner-Kontext (Konsole/Hub).
Export-Pipeline ist der symmetrische Weg zurück: Standard-konformes IAR/OAR, das jedes klassische OpenSim wieder einliest; pro Asset wird bevorzugt origin_sha256 verwendet → Alt-Grid erhält die bit-identische Original-Datei zurück.
Warum das alles löst: DB bleibt schlank (nur Metadaten), Cleanup funktioniert (eine Referenz-Tabelle, ein Mark-and-Sweep), Backups trivial, echte Deduplication gratis, Zero-Copy + ewig cachebar, Integrität bewiesen (Hash + chmod 0444), Migration ohne Neubau, kein Vendor-Lock.
5. Scripting: LSL/OSSL kompatibel, aber massiv schneller
Zwei-Stufen-Engine (eine Engine nach außen):
Stufe A – AST/Bytecode-Interpreter (zuerst): LSL-Parser (pest/nom) → typisierter AST/eigener Bytecode → schneller Rust-Interpreter in gridnode-scripts. Der gesamte Skript-Zustand (Variablen, aktueller state, Event-Queue, Timer-Rest, Listener) ist eine eigene, serde-fähige Struktur → trivial serialisierbar für Region-Crossing.
Stufe B – WASM-Beschleuniger (danach): heiße Skripte laufen als WASM in wasmtime (Sandbox, Multi-Core, Instance-Pool). Weg dorthin: LSL-AST → Rust → wasm32 (LLVM-Optimierung), nicht eigener WASM-Codegen. Kompilat wird als serialisiertes wasmtime-Modul auf Platte gecacht (gegen Cold-Start beim Region-Start).
State-Ownership bleibt bei der AST-Ebene, nie im WASM-Linearspeicher – so bleibt der Zustand über Regionsgrenzen sauber transportierbar.
Determinismus: wasmtime mit consume_fuel (abgezählte Instruktionen = Skript-Zeitbudget) + nan_canonicalization = true + fixierter Cranelift-/Feature-Matrix (analog Rapier enhanced-determinism) → Basis für späteren Rollback-Netcode.
OSSL-Kompat-Layer für die gängigen os*-Funktionen. Reales OpenSim-Script aus einem Alt-Grid läuft unverändert.
Script-Debug-CAP (später) – ohne Region-Restart-Crash.
Verifikations-Ziel: 100 parallele Scripts ohne Region-TPS-Einbrüche; Cold-Start via Cache