Vortrag über Ideen für eine neue Sprache für Spieleentwickler [youtube]



  • Ja weisste, doof stellen kann ich mich auch.



  • Erklaer doch mal, was an der Loesung nicht passt. Duerfte so ziemlich genau tun, was du erklaert hast. Oder du hast einfach die Anforderungen nicht genau genug erklaert :p



  • Die Endpoint-Connection Objekte werden natürlich an verschiedene andere Objekte übergeben (=Ownership-Transfer), die sich so gar nicht darum scheren wie die Endpoint-Connections implementiert sind, und dass die evtl. einen gemeinsam benutzten Channel haben.
    Genau das ist nämlich vernünftiges API Design.

    Weitere Beispiele:

    Ein Video-Control (GUI-Control, "Widget") das einen "Renderer" braucht um sein Video auf den Schirm zu klatschen. Der Renderer verwendet z.B. D3D oder OpenGL, und du musst 20+ solche Video Controls gleichzeitig unterstützen. In Fenstern die der User nach belieben auf und zumachen kann.
    Die Video-Controls sollten sich also sinnvollerweise einen Renderer teilen, da der Renderer ein nicht ganz billiges Objekt ist (z.B. ein D3DDevice/OGL-Context, ein Shader, mehrere Temp-Texturen).

    Die Videos kommen von Kameras. (Ist jetzt kein eigener Punkt mehr, sondern ein Beispiel für (1)). Die Kameras unterstützen nur eine begrenzte Anzahl an gleichzeitigen "User-Sessions". Man kann also nicht für jeden Scheiss eine eigene aufmachen. Eine User-Session ist nötig um das Livebild zu bekommen und genau so ist eine User-Session nötig um z.B. Bewegungsalarme zu empfangen. Und natürlich um eine Aufzeichnung zu starten, die Kamera zu steuern etc.
    Dummerweise war es dem Entwickler der Kamera-API aber zu doof hier selbst Shared Ownership zu implementieren, und dafür zu sorgen dass z.B. die Livebild-Connection auch die User-Session am Leben hält. Machst du die User-Session zu, "stirbt" damit auch die Livebild-Connection.
    Deine Livebild-Connection Klassen müssen sich also Ownership des User-Session-Handles teilen. Und zwar möglicherweise untereinander (es gibt Fälle wo mehrere Livebild-Connection zur selben Kamera nötig sind, z.B. mit unterschiedlichen Auflösungen/Formaten), als auch mit der User-Session Klasse selbst, die anderorts benötigt wird um eben die Alarme reinzubekommen oder die Kamera zu steuern (bewegen/zoomen/...).
    Und "anderorts" sind einmal Services die im Hintergrund laufen, und dann wieder andere Fenster, die der User auch wieder nach Belieben auf- und zumachen kann.

    Inversion of Control mit nem Service-Locator.
    Da sind verschiedene Services drin registriert, die sich auch untereinander verwenden, und weder der IOC-Container noch der Service-Locator können wissen in welcher Reihenfolge die ganze Show zerstört werden muss damit es kein "use after free" gibt.

    Caches.

    Du hast ne Anwendung in der du irgendwelche "Dokumente" suchen kannst. Ob als headless Service oder GUI ist im Prinzip egal. Der bzw. die User können da Suchanfragen schicken. Die Ergebnisse kommen aus einer Search-Index Klasse die das übernimmt. Dahinter steht keine klassische Datenbank-Connection mit Transaktionen und allem, sondern z.B. ein Lucene Index.
    Hin und wieder muss der Index aktualisiert werden. Das passiert im Hintergrund indem einfach ein neuer Index aufgebaut wird, weil das die eleganteste und auch performanteste Lösung ist. Der neue Index wird parallel zum alten aufgebaut, und wenn er fertig ist soll der alte weggeworfen werden und ab da dann der neue verwendet.
    Die Suchergebnisse die der Index ausspuckt sind aber bloss eine Liste von IDs. Die Daten selbst müssen über die Dokument-ID aus dem Index bezogen werden. Alle Daten über alle Dokumente sofort abzufragen ist nicht vertretbar, da es viel zu lange dauern würde. Der User sieht immer nur ein paar wenige auf einmal so lange er nicht "blättert".
    Die Suchergebnisse müssen aber gültig bleiben während der User drinnen rumstöbert. Die Suchergebnisse müssen also den Index am Leben halten bis er nicht mehr benötigt wird.

    Natürlich wäre es möglich die Suche beim blättern jedes mal zu wiederholen, aber das hat ein paar unerwünschte Nebeneffekte - z.B. dass der User bestimmte Ergebnisse nie sieht, weil sie zwischen der Anfrage für Seite 1 und der für Seite 2 von Seite 2 auf Seite 1 "verrutscht" sind. Und natürlich ist die Performance u.U. deutlich schlechter.
    Aber angenommen damit kannst du leben ... ergibt sich dann gleich das nächste Problem: du kannst den alten Index nicht gegen den neuen austauschen so lange noch irgendwelche Threads IN einer Suchfunktion mit dem alten Index stecken. D.h. du brauchst erst wieder Shared-Ownership um den alten Index so lange am Leben zu halten wie es Threads gibt die darauf zugreifen. ODER du musst über eine zentrale Reader-Writer-Mutex sicherstellen dass keine Reader mehr auf den Index zugreifen während du ihn auswechselst. Das blockiert neue Anfragen so lange bis alle die gerade "in Ausführung" sind fertig gelaufen sind.
    Klar, damit kann man u.U. leben, aber es sind zwei Nachteile die man nicht haben müsste, wenn man einfach akzeptiert dass Shared-Ownership etwas ganz normales ist, und man nicht dagegen ankämpfen sollte.
    Und lustigerweise wirst du selbst damit die Shared-Ownership nicht ganz los - du verschiebst sie bloss in die Reader-Writer-Mutex, so dass du sie selbst "nicht mehr sehen musst". Denn die "Read-Locks" auf die Reader-Writer-Mutex die die Abfrage-Threads halten müssen sind auch nix anderes als Shared-Ownership. (In C++ heisst die Read-Lock Klasse sinnvollerweise sogar std::shared_lock ).

    GUI bzw. 3D Engines. Hier gibt es zwei mir bekannte sinnvolle Varianten. Die eine ist einfach, und verwendet Shared-Ownership für Dinge wie Texturen, Shader, Geometry etc. (die 3D Engines die ich kenne machen das so). Die andere ist deutlich aufwendiger und verwendet Resource-Manager Klassen von denen die nötigen Resourcen jedes mal wenn sie benötigt werden anhand ihrer ID neu angefordert werden.



  • TyRoXx schrieb:

    C# fällt weg, weil Mono unter aller Sau buggy ist. Visual Studio ist einfach lächerlich zurückgeblieben.
    Apple-Müllsprachen fallen natürlich auch weg.
    Java ist scheiße und tot. Es gibt keine vernünftige Entwicklungsumgebung. Soll ich ernsthaft eine Programmiersprache einsetzen, die standardmäßig Malware ("Toolbars") mitliefert?
    Ich benutze C++, weil es im Gegensatz zu anderen Sprachen funktioniert. Es gibt zwei brauchbare Compiler (GCC, Clang), eine brauchbare IDE (QtCreator) und kann zur Not ohne IDE sinnvoll benutzt werden.

    Hast du gesoffen?



  • hustbaer schrieb:

    Inversion of Control mit nem Service-Locator.
    Da sind verschiedene Services drin registriert, die sich auch untereinander verwenden, und weder der IOC-Container noch der Service-Locator können wissen in welcher Reihenfolge die ganze Show zerstört werden muss damit es kein "use after free" gibt.

    Das macht aber auch keinen Spaß ohne einen GC. Außer du beweist immer, daß es keine zirkulären Abhängigkeiten gibt.



  • Ethon schrieb:

    TyRoXx schrieb:

    C# fällt weg, weil Mono unter aller Sau buggy ist. Visual Studio ist einfach lächerlich zurückgeblieben.
    Apple-Müllsprachen fallen natürlich auch weg.
    Java ist scheiße und tot. Es gibt keine vernünftige Entwicklungsumgebung. Soll ich ernsthaft eine Programmiersprache einsetzen, die standardmäßig Malware ("Toolbars") mitliefert?
    Ich benutze C++, weil es im Gegensatz zu anderen Sprachen funktioniert. Es gibt zwei brauchbare Compiler (GCC, Clang), eine brauchbare IDE (QtCreator) und kann zur Not ohne IDE sinnvoll benutzt werden.

    Hast du gesoffen?

    Zumindest den Teil mit der Toolbar kann ich nachvollziehen.



  • Also ne Toolbar mußte ich bei Java nie mitinstallieren. Ich bin wirklich kein Java-Fan, aber fair sollte man schon noch bleiben. 🙄



  • Artchi schrieb:

    Also ne Toolbar mußte ich bei Java nie mitinstallieren. Ich bin wirklich kein Java-Fan, aber fair sollte man schon noch bleiben. 🙄

    Gemeint ist der nicht versteckte Installer für Windows. Es gibt noch andere, die keine Malware installieren.



  • Artchi schrieb:

    Also ne Toolbar mußte ich bei Java nie mitinstallieren. Ich bin wirklich kein Java-Fan, aber fair sollte man schon noch bleiben. 🙄

    Siehe hier. Einfach nur 'ne lächerliche Frechheit dieses Java. Bei einer Runtime drehen die einem Adware an... 👎



  • Ich hatte noch nie eine Java-Toolbar installiert. Und selbst wenn ist Java sehr weit entfernt von tot und es gibt viele und sehr gute Entwicklungsumgebungen.



  • volkard schrieb:

    Du stellst Dich anscheinend als Verteidiger der "C-in-C++"-Programmierer hin,

    Das tue ich nicht. Ich bin keiner von den "C-in-C++"-Programmierern. Ich stecke bei Dir nur fälschlicherweise in so einer Schublade drin.

    volkard schrieb:

    sagst die typischen C-Fehler seien in C++ voll normal.

    Das kann ich auch nicht bestätigen, wenn Du es so formulierst. Ich denke, wir belegen "typische C-Fehler" mit einer anderen Bedeutung. Ich sage, dass bzgl Speichersicherheit C++ nicht wirklich viel besser als C darsteht. Ein bisschen. Nicht viel.

    Zum Beispiel bieten einige Standardbibliotheksimplementierungen zu Debugzwecken so etwas wie "checked iterators" und co an und std::vector<T>::operator[] sowie std::array<T,N>::operator[] werden in so einem Modus auch testen, ob der Index gültig ist. Das ist prima! Deswegen würde ich sagen, C++ ist "ein bisschen besser" bzgl Speichersicherheit, weil es zumindest im Debugmodus bei gescheiter Nutzung der Standardbibliothek zur Laufzeit einige Fehler besser/früher abfangen kann.

    Aber diese Art der Fehlererkennung führt leider dazu, dass die Programme sehr langsam laufen. Das ist zumindest bei g++ der Fall gewesen, last time I checked. Deswegen verwende ich so einen Modus auch nur dann, wenn mir mein C++ Programm mal um die Ohren geflogen ist. Das passiert extrem selten, so selten, dass ich mich nicht mehr daran erinnere, was das für Fehler waren. Aber meine Anwendungsdomäne (number crunching command line tools) ist da auch kein großes Problem in der Hinsicht. Diese Fehlererkennungsmöglichkeiten sind leider auch etwas löchrig. Cooler wär's natürlich, wenn alle Arten von "Referenzen", die ungültig geworden sind, irgendwie abgefangen werden könnten, nicht nur Iteratoren. Und noch cooler wär's, wenn der Großteil dieser Checks schon zur Compilezeit laufen könnten und keine Laufzeitkosten verursachen würden. Dann kann man es sich sogar erlauben, die restlichen Checks drin zu lassen. Denn im Endeffekt wirst Du den C++ Code im "release mode" kompilieren und laufen lassen. Es kommt dann darauf an, ob du bei den vorherigen Testläufen schon alle Fehler abfangen konntest. Ich stelle es mir schwierig vor, Testfälle zu erzeugen, die alle Sicherheitslücken abdecken werden. Denn sonst gäb's nicht so viele Sicherheitslücken in C++ Software.

    Es ist super easy mit einer kleinen Recherche Sicherheitsprobleme in C++ Software zu finden. Ein nicht zu verachtender Teil sind Spechersicherheitsfehler (u.a. use-after-free). Und da ist C++ Software bei, die nicht von Idioten geschrieben wurden, sondern von Leuten wie Du und ich, die "von C++ Ahnung" haben. Statt das anzuerkennen sprichst Du Leuten reflexartig Kompetenzen ab, ohne wirklich einen Überblick über deren Anwendungsdomäne zu haben. Natürlich kommt mir das ignorant und eingebildet vor. Es hilft auch keinem. Es verschlechtert nur den Signal-Rausch-Abstand.

    volkard schrieb:

    Kellerautomat schrieb:

    Es gibt einfach keine Faelle, in denen Exceptions irgendwie sinnvoll sind. Halt, doch. Da war doch was mit Konstruktoren. Hm, vielleicht einfach Konstruktoren entfernen und Funktionen bereitstellen? Koennte klappen. Oder einfach nix im Konstruktor machen, was fehlschlagen koennte. Hey, das klingt doch gut.

    Gefällt mir. Die Sonderrolle der Konstruktoren war mir immer zuwider.

    volkard schrieb:

    Kellerautomat schrieb:

    Andererseits, fuer Sachen wie mathematische Vektoren ist es doch ganz praktisch, wenn ich vector3f(1.f, 2.f, 3.f) schreiben kann. Hmmmm...

    Ja, vector3f(1.f, 2.f, 3.f) ist toll, ist nur Aufrufmagie. Die andere Seite stört mich. Konstruktoren sind static-Methoden, ohne daß ein static da steht. Sie haben einen seltsamen Namen, statt wie in anderen Sprachen new, _construct oder so. Die haben keinen Rückgabetyp, nichtmal void.

    volkard schrieb:

    Ja, auch ein Punkt, den ich doof finde: Objekte wegmoven, ohne daß der Bezeichner verschwindet. Ich hätte gerne

    ding.foo();
    aufruf(move(ding));
    //ding.foo();//gäbe compilerfehler
    

    volkard, das sind super Steilvorlagen von Dir, denen ich nicht entziehen kann: In Rust gibt es diese destruktiven Moves und statt Konstruktoren statische Methoden und Funktionen. Es gibt da auch eine Lösung für das Problem, Member aus Objekten rauszumoven, ohne dass man immer Option benutzen muss (kommt auf den Fall an). Es gibt noch replace und Methoden, die ihr Objekt konsumieren können, so dass der Aufrufer es nicht mehr weiter benutzen kann. In diesen Methoden darf man natürlich das Objekt komplett auseinander nehmen.

    IBV schrieb:

    Scala kennt Null nur von Java, benutzt es in eigenen Libs allerdings nicht. Es wird höchstens None (eher bei Listen. Alle Operationen sind jedoch noch anwendbar.) zurückgegeben oder ein bestimmter Fehlertyp (bei der Verarbeitung von Daten).
    Z. B.:

    request.body.validate[User] match {
      case jsUser: JsSuccess[User] => // Do something
      case error: JsError => // Error Handling
    }
    

    Allgemeine Fehlerbehandlung (diese Konstellation taucht eher selten auf):

    request.body.validate[User] match {
      case jsUser: JsSuccess[User] => // Do something
      case _ => // General Error Handling
    }
    

    Interessant. Das sieht in Rust fast genauso aus. Gibt es in Scala auch so etwas wie das Haskell- do oder das Rust- try! , womit man sich das manuelle match en in vielen Fällen sparen kann?

    type MyResult<T> = Result<T, SendStr>;
    //                           ^^^^^^^ beliebiger Typ für den Fehlerfall
    // SendStr kann String-Objekte oder Verweise auf Stringliterale speichern
    
    fn foo(i: int) -> MyResult<int> {...}
    fn bar(i: int) -> MyResult<int> {...}
    
    fn dings(i: int) -> MyResult<int> {
        try!(foo(i)) * 2 + try!(bar(i+3))
    }
    
    fn main() {
       match dings(23) {
          Err(msg) => println!("Klappte nicht. Fehlermeldung: {}", msg),
          Ok(i) => println!("Das Ergebnis ist {}", i)
       }
    }
    

    wobei try! hier im Normalfall den erwarteten Wert auspackt und im Fehlerfall die Funktion mit dem Fehlerwert per return frühzeitig beendet. Aber es funktioniert bis jetzt nur, wenn der Fehlertyp derselbe ist. Man arbeitet z.Zt. daran, es etwas aufzuboren und benutzerdefinierte Konvertierungen von Fehlertypen zuzulassen. Result bietet auch ein paar nützliche Methoden an, mit denen man sich das eine oder andere match sparen kann.

    In C++ nutze ich Ausnahmen hauptsächlich dazu, Fehlermeldungen bzgl Eingabefehler des Benutzers zur catch -Klausel an main durchzureichen. Wenn man RAII/SBRM konsequent einsetzt, seh' ich da auf Anhieb keine großen Probleme. Ausnahmesicherheit ist für mich aber nicht der einzige Grund für RAII/SBRM.



  • krümelkacker schrieb:

    Interessant. Das sieht in Rust fast genauso aus. Gibt es in Scala auch so etwas wie das Haskell- do oder das Rust- try! , womit man sich das manuelle match en in vielen Fällen sparen kann?

    Nicht, dass ich wüsste, aber das match kann man sich teils auch mit getOrElse sparen.
    Die einfache Variante sieht so aus: http://whileonefork.blogspot.de/2011/05/magic-of-getorelse.html
    Die komplexere mit map so:

    request.session.get(UUID_KEY).map { uuid =>
      // Do something
    }.getOrElse {
      BadRequest("UUID could not be mapped to a captcha solution. Only one try per captcha possible.")
    }
    

    L. G.,
    IBV



  • krümelkacker schrieb:

    Cooler wär's natürlich, wenn alle Arten von "Referenzen", die ungültig geworden sind, irgendwie abgefangen werden könnten, nicht nur Iteratoren.

    Clang hat den Address Sanitizer, der schon einiges mit nur rund 100% Overhead zur Laufzeit finden soll.



  • IBV schrieb:

    Nicht, dass ich wüsste, aber das match kann man sich teils auch mit getOrElse sparen.

    Solche und ähnliche Operationen sind auch praktisch manchmal. (In Rust z.B. unwrap_or , unwrap_or_else )

    TyRoXx schrieb:

    krümelkacker schrieb:

    Cooler wär's natürlich, wenn alle Arten von "Referenzen", die ungültig geworden sind, irgendwie abgefangen werden könnten, nicht nur Iteratoren.

    Clang hat den Address Sanitizer, der schon einiges mit nur rund 100% Overhead zur Laufzeit finden soll.

    Achja, Clang's AdressSanatizer gibt's ja auch noch. Den habe ich noch nicht getestet. Von einem anderen Tool, was die Ausfürhung auch "nur halb so schnell" werden lassen soll, habe ich hier gehört. Den Vortrag kann man sich auch angucken. Ich fand den nicht schlecht.

    Eine Version, die halb so schnell läuft und dabei alle "Speicherfehler" abfangen kann, reicht wahrscheinlich für Testläufe. Und für manche Anwendungen ist das vllt auch schnell genug, um es produktiv einzusetzen, wenn man sich Sorgen um Angreifer macht, die einbrechen wollen.



  • krümelkacker schrieb:

    Interessant. Das sieht in Rust fast genauso aus. Gibt es in Scala auch so etwas wie das Haskell- do oder das Rust- try! , womit man sich das manuelle match en in vielen Fällen sparen kann?

    type MyResult<T> = Result<T, SendStr>;
    //                           ^^^^^^^ beliebiger Typ für den Fehlerfall
    // SendStr kann String-Objekte oder Verweise auf Stringliterale speichern
    
    fn foo(i: int) -> MyResult<int> {...}
    fn bar(i: int) -> MyResult<int> {...}
    
    fn dings(i: int) -> MyResult<int> {
        try!(foo(i)) * 2 + try!(bar(i+3))
    }
    
    fn main() {
       match dings(23) {
          Err(msg) => println!("Klappte nicht. Fehlermeldung: {}", msg),
          Ok(i) => println!("Das Ergebnis ist {}", i)
       }
    }
    

    http://www.scala-lang.org/api/current/#scala.util.Try


Anmelden zum Antworten