Skelettanimation (Teil 1 – Shader)

In diesem Artikel werde ich mein Verständnis der Skelettanimation beschreiben, die in allen modernen 3D-Engines zur Animation von Charakteren, Spielumgebungen usw. verwendet wird.
Ich beginne die Beschreibung mit dem greifbarsten Teil – – Vertex-Shader, denn der gesamte Berechnungspfad, egal wie komplex er auch sein mag, endet mit der Übergabe der vorbereiteten Daten zur Anzeige an den Vertex-Shader.

Die Skelettanimation geht nach der Verarbeitung auf der CPU in den Vertex-Shader.
Ich möchte Sie an die Formel für Scheitelpunkte ohne Skelettanimation erinnern:
gl_Position = projectMatrix * viewMatrix * modelMatrix * vertex;
Für diejenigen, die nicht verstehen, wie diese Formel zustande kam, können Sie meinen Artikel lesen, der das Prinzip der Arbeit mit Matrizen zur Anzeige von 3D-Inhalten im Kontext von OpenGL beschreibt.
Im Übrigen – Formel zur Implementierung einer Skelettanimation:
” vec4animierterVertex = bone0matrix * vertex * bone0weight +”
“bone1matrix * vertex * bone1weight +”
“bone2matrix * vertex * bone2weight +”
“bone3matrix * vertex * bone3weight;\n”
” gl_Position = projectMatrix * viewMatrix * modelMatrix * animatedVertex;\n”

Das heißt, wir multiplizieren die endgültige Knochentransformationsmatrix mit dem Scheitelpunkt und mit dem Gewicht dieser Matrix relativ zum Scheitelpunkt. Jeder Scheitelpunkt kann durch 4 Knochen animiert werden, die Stärke des Aufpralls wird durch den Knochengewichtsparameter reguliert, die Summe der Aufschläge sollte gleich eins sein.
Was tun, wenn weniger als 4 Knochen den Scheitelpunkt betreffen? Wir müssen das Gewicht zwischen ihnen aufteilen und die Auswirkung des Rests auf Null setzen.
Mathematisch wird die Multiplikation einer Gewichtung mit einer Matrix „Matrix-Skalar-Multiplikation“ genannt. Durch Multiplikation mit einem Skalar können Sie die Wirkung der Matrizen auf den resultierenden Scheitelpunkt zusammenfassen.

Die Knochentransformationsmatrizen selbst werden als Array übertragen. Darüber hinaus enthält das Array Matrizen für das gesamte Modell als Ganzes und nicht für jedes Netz einzeln.

Aber für jeden Scheitelpunkt werden die folgenden Informationen separat übertragen:
– Index der Matrix, die den Scheitelpunkt beeinflusst
– Gewicht der Matrix, das den Scheitelpunkt beeinflusst
Es wird mehr als ein Knochen übertragen, meist wird die Wirkung von 4 Knochen am Scheitelpunkt genutzt.
Außerdem muss die Summe der Gewichte der 4 Würfel immer gleich eins sein.
Schauen wir uns als Nächstes an, wie es im Shader aussieht.
Matrix-Array:
“uniform mat4 bonesMatrices[kMaxBones];”

Informationen über die Wirkung von 4 Knochen auf jedem Scheitelpunkt:
“Attribut vec2 bone0info;”
“Attribut vec2 bone1info;”
“Attribut vec2 bone2info;”
“attribute vec2 bone3info;”

vec2 – In der X-Koordinate speichern wir den Index des Knochens (und konvertieren ihn im Shader in int), in der Y-Koordinate speichern wir das Gewicht des Aufpralls des Knochens auf den Scheitelpunkt. Warum müssen Sie diese Daten in einem zweidimensionalen Vektor übertragen? Weil GLSL die Übergabe von C-lesbaren Strukturen mit gültigen Feldern an den Shader nicht unterstützt.

Im Folgenden gebe ich ein Beispiel für den Erhalt der notwendigen Informationen aus einem Vektor für die weitere Substitution in die animierteVertex-Formel:

“int bone0Index = int(bone0info.x);”
“float bone0weight = bone0info.y;”
“mat4 bone0matrix = bonesMatrices[bone0Index];”

“int bone1Index = int(bone1info.x);”
“float bone1weight = bone1info.y;”
“mat4 bone1matrix = bonesMatrices[bone1Index];”

“int bone2Index = int(bone2info.x);”
“float bone2weight = bone2info.y;”
“mat4 bone2matrix = bonesMatrices[bone2Index];”

“int bone3Index = int(bone3info.x);”
“float bone3weight = bone3info.y;”
“mat4 bone3matrix = bonesMatrices[bone3Index];”

Jetzt sollte die auf der CPU gefüllte Vertex-Struktur wie folgt aussehen:
x, y, z, u, v, Bone0index, Bone0weight, Bone1index, Bone1weight, Bone2index, Bone2weight, Bone3index, Bone3weight

Die Vertex-Pufferstruktur wird beim Laden des Modells einmal gefüllt, aber Transformationsmatrizen werden bei jedem Rendering-Frame von der CPU an den Shader übertragen.

In den verbleibenden Teilen beschreibe ich das Prinzip der Animationsberechnung auf der CPU. Bevor ich sie an den Vertex-Shader übertrage, beschreibe ich den Baum der Knochenknoten und gehe durch die Hierarchie Animation-Modell-Knoten-Netz, Matrix Interpolation.

Quellen

http://ogldev.atspace.co. uk/www/tutorial38/tutorial38.html

Quellcode

https://gitlab.com/demensdeum/skeletal-animation

Vorlagenmethode

Die Mustermethode bezieht sich auf Verhaltensentwurfsmuster. Das Muster beschreibt eine Möglichkeit, einen Teil der Logik einer Klasse bei Bedarf zu ersetzen, wobei der Gesamtteil für Nachkommen unverändert bleibt.

Cuban Cars

Angenommen, wir entwickeln eine Kundenbank, denken Sie über die Aufgabe nach, ein Autorisierungsmodul zu entwickeln – Der Benutzer muss sich mit abstrakten Anmeldedaten in die Anwendung einloggen können.
Das Autorisierungsmodul muss plattformübergreifend sein, verschiedene Autorisierungstechnologien unterstützen und verschlüsselte Daten verschiedener Plattformen speichern. Um das Modul zu implementieren, wählen wir die plattformübergreifende Kotlin-Sprache. Mithilfe der abstrakten Klasse (Protokoll) des Autorisierungsmoduls schreiben wir eine Implementierung für das MyPhone-Telefon:

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    abstract fun authorize(authorizationData: AuthorizationData)
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
    override fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Jetzt müssen wir für jedes Telefon/jede Plattform den Code zum Senden der Autorisierung an den Server duplizieren. Dies stellt einen Verstoß gegen das DRY-Prinzip dar. Das obige Beispiel ist sehr einfach, in komplexeren Klassen wird es noch mehr Duplikate geben. Um Codeduplizierungen zu vermeiden, sollten Sie das Template-Methodenmuster verwenden.
Lassen Sie uns die gemeinsamen Teile des Moduls in unveränderliche Methoden verschieben und die Funktionalität der verschlüsselten Datenübertragung auf bestimmte Plattformklassen übertragen:

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    
    fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Quellen

https://refactoring.guru/ru/design- Muster/Vorlagenmethode

Quellcode

https://gitlab.com/demensdeum/patterns/< /p>

Musterbrücke

Das Brückenmuster bezieht sich auf strukturelle Entwurfsmuster. Sie können die Implementierung der Klassenlogik abstrahieren, indem Sie die Logik in eine separate abstrakte Klasse verschieben. Klingt einfach, oder?

Angenommen, wir implementieren einen Spam-Bot, der in der Lage sein sollte, Nachrichten an verschiedene Arten von Messenger zu senden.
Wir implementieren es mithilfe eines gemeinsamen Protokolls:

protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class SpamBot {
    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            iSeekUMessennger.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

Stellen wir uns nun die Veröffentlichung eines neuen, schnelleren Protokolls zum Senden von Nachrichten für den iSekU-Messenger vor. Um ein neues Protokoll hinzuzufügen, müssen Sie die Implementierung des iSekU-Bots duplizieren und nur einen kleinen Teil davon ändern. Es ist nicht klar, warum dies getan werden sollte, wenn sich nur ein kleiner Teil der Klassenlogik geändert hat. Bei diesem Ansatz wird das DRY-Prinzip verletzt; bei Weiterentwicklung des Produkts macht sich die fehlende Flexibilität durch Fehler und Verzögerungen bei der Implementierung neuer Features bemerkbar.
Verschieben wir die Logik des Protokolls in eine abstrakte Klasse und implementieren so das Bridge-Muster:

protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

protocol MessagesSender {
    func send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekUFastMessengerSender: MessagesSender {
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?
    var messagesSender: MessengerMessagesSender?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        messagesSender?.send(message: message, to: user)
    }
}

class SpamBot {

    var messagesSender: MessagesSender?

    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            messagesSender.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

Einer der Vorteile dieses Ansatzes ist zweifellos die Möglichkeit, die Funktionalität der Anwendung durch das Schreiben von Plugins/Bibliotheken zu erweitern, die abstrahierte Logik implementieren, ohne den Code der Hauptanwendung zu ändern.
Was ist der Unterschied zum Strategiemuster? Beide Muster sind sehr ähnlich, jedoch beschreibt Strategy das Umschalten von *Algorithmen*, während Bridge es Ihnen ermöglicht, große Teile *jeder komplexen Logik* zu wechseln.

Quellen

https://refactoring.guru/ru/design-patterns/bridge

Quellcode

https://gitlab.com/demensdeum/patterns/p>

Muster der Verantwortungskette

Verantwortungskette bezieht sich auf Verhaltensmuster.


Ganna Dolbieva

Die Filmfirma Jah-Pictures drehte einen Dokumentarfilm über kommunistische Rastafarians aus Liberia mit dem Titel „Red Dawn of Marley“. Der Film ist sehr lang (8 Stunden) und interessant, aber vor seiner Veröffentlichung stellte sich heraus, dass in einigen Ländern Szenen und Phrasen aus dem Film als Ketzerei gelten und keine Vertriebslizenz erhalten. Die Produzenten des Films beschließen, manuell und automatisch Momente mit fragwürdigen Phrasen aus dem Film herauszuschneiden. Eine doppelte Kontrolle ist erforderlich, damit die Vertreter des Händlers in einigen Ländern nicht einfach erschossen werden, falls bei der manuellen Inspektion und Installation ein Fehler auftritt.
Die Länder werden in vier Gruppen eingeteilt – Länder ohne Zensur, mit mäßiger, mittlerer und sehr strenger Zensur. Es wird beschlossen, neuronale Netze zu verwenden, um den Grad der Häresie im angesehenen Filmausschnitt zu klassifizieren. Für das Projekt werden sehr teure hochmoderne Neuronen angeschafft, auf unterschiedliche Zensurstufen trainiert, die Aufgabe des Entwicklers – Brechen Sie den Film in Fragmente auf und übertragen Sie sie durch eine Kette neuronaler Netze, von frei bis streng, bis eines von ihnen Häresie erkennt. Anschließend wird das Fragment zur manuellen Überprüfung zur weiteren Bearbeitung übertragen. Es ist unmöglich, alle Neuronen zu durchlaufen, weil Ihre Arbeit erfordert zu viel Rechenleistung (schließlich müssen wir immer noch für Strom bezahlen), es reicht aus, bei der ersten anzuhalten, die funktioniert.
Naive Pseudocode-Implementierung:

import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier) {
        self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        return hnnclassifier.shouldBeCensored(movieChunk)
    }
}

let lightCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"))
let normalCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal"))
let hardCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard"))

let classifiers = [lightCensorshipClassifier, normalCensorshipClassifier, hardCensorshipClassifier]

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    for classifier in classifiers {
        if classifier.shouldBeCensored(chunk) == true {
            print("Should censor movie chunk: \(chunk), reported by \(classifier)")
        }
   }
}

Im Allgemeinen ist die Lösung mit einem Array von Klassifikatoren jedoch nicht so schlecht! Stellen wir uns vor, dass wir kein Array erstellen können, wir haben die Möglichkeit, nur eine Klassifikatoreinheit zu erstellen, die bereits die Art der Zensur für ein Filmfragment bestimmt. Solche Einschränkungen sind möglich, wenn eine Bibliothek entwickelt wird, die die Funktionalität der Anwendung (Plugin) erweitert.
Verwenden wir das Dekoratormuster – Fügen wir der Klassifikatorklasse einen Verweis auf den nächsten Klassifikator in der Kette hinzu und stoppen den Überprüfungsprozess bei der ersten erfolgreichen Klassifizierung.
Daher implementieren wir das Chain of Responsibility-Muster:

import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let nextClassifier: CensorshipClassifier?
    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier, nextClassifier: CensorshipClassifiers?) {
            self.nextClassifier = nextClassifier
            self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        let result = hnnclassifier.shouldBeCensored(movieChunk)
        
        print("Should censor movie chunk: \(movieChunk), reported by \(self)")
        
        if result == true {
                return true
        }
        else {
                return nextClassifier?.shouldBeCensored(movieChunk) ?? false
        }
    }
}

let censorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"), nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal", nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard")))))

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    censorshipClassifier.shouldBeCensored(chunk)
}

Referenzen

https://refactoring.guru/ru/ Designmuster/Verantwortungskette

Quellcode

https://gitlab.com/demensdeum/patterns/< /p>

Musterdekorateur

Das Decorator-Muster bezieht sich auf strukturelle Designmuster.

Der Dekorator wird als Alternative zur Vererbung verwendet, um die Funktionalität von Klassen zu erweitern.
Je nach Produkttyp besteht die Aufgabe, die Funktionalität der Anwendung zu erweitern. Der Kunde benötigt drei Arten von Produkten – Einfach, professionell, ultimativ.
Basic– zählt die Anzahl der Zeichen, Professional – Funktionen Basic + druckt Text in Großbuchstaben, Ultimate – Basic + Professional + druckt Text mit der Aufschrift ULTIMATE.
Wir implementieren es mithilfe der Vererbung:

protocol Feature {
	func textOperation(text: String)
}

class BasicVersionFeature: Feature {
	func textOperation(text: String) {
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: ProfessionalVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)

Jetzt besteht die Anforderung, das Produkt „Ultimate Light“ umzusetzen – Basic + Ultimate, jedoch ohne die Funktionen der Professional-Version. Das erste OH! passiert, weil… Sie müssen für eine so einfache Aufgabe eine separate Klasse erstellen und den Code duplizieren.
Lassen Sie uns die Implementierung mithilfe der Vererbung fortsetzen:

protocol Feature {
	func textOperation(text: String)
}

class BasicVersionFeature: Feature {
	func textOperation(text: String) {
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: ProfessionalVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

class UltimateLightVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")	
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)

let ultimateLightProduct = UltimateLightVersionFeature()
ultimateLightProduct.textOperation(text: textToFormat)

Das Beispiel kann zur Verdeutlichung weiterentwickelt werden, aber schon jetzt ist die Komplexität der Unterstützung eines Systems auf Basis einer Vererbungsbasis sichtbar – umständlich und mangelnde Flexibilität.
Ein Dekorator ist eine Reihe von Protokollen, die die Funktionalität beschreiben, eine abstrakte Klasse, die einen Verweis auf eine untergeordnete konkrete Instanz der Dekoratorklasse enthält, die die Funktionalität erweitert.
Schreiben wir das obige Beispiel mit dem Muster um:

protocol Feature {
	func textOperation(text: String)
}

class FeatureDecorator: Feature {
	private var feature: Feature?
	
	init(feature: Feature? = nil) {
		self.feature = feature
	}
	
	func textOperation(text: String) {
		feature?.textOperation(text: text)
	}
}

class BasicVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature(feature: UltimateVersionFeature())
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature(feature: UltimateVersionFeature())
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = BasicVersionFeature(feature: UltimateVersionFeature(feature: ProfessionalVersionFeature()))
ultimateProduct.textOperation(text: textToFormat)

let ultimateLightProduct = BasicVersionFeature(feature: UltimateVersionFeature())
ultimateLightProduct.textOperation(text: textToFormat)

Jetzt können wir Variationen jeder Art von Produkt erstellen – Es reicht aus, die kombinierten Typen beim Anwendungsstart zu initialisieren. Das folgende Beispiel zeigt die Erstellung der Ultimate + Professional-Version:

ultimateProfessionalProduct.textOperation(text: textToFormat)

Quellen

https://refactoring.guru/ru/design-patterns/decorator

Quellcode

https://gitlab.com/demensdeum/patterns

Mustervermittler

Das Mediator-Muster bezieht sich auf Verhaltensdesignmuster.

Eines Tages erhalten Sie den Auftrag, eine Scherzanwendung zu entwickeln – Der Benutzer drückt eine Taste in der Mitte des Bildschirms und er ertönt das lustige Quaken einer Ente.
Nach dem Hochladen in den App Store wird die Anwendung zum Hit: Alle quakten über Ihre Anwendung, Elon Musk quakt auf seinem Instagram beim nächsten Start eines Super-Hochgeschwindigkeitstunnels auf dem Mars, Hillary Clinton quält Donald Trump bei der Debatte und gewinnt die Wahlen in der Ukraine, Erfolg!
Die naive Implementierung der Anwendung sieht folgendermaßen aus:

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

Als nächstes entscheiden Sie sich, das Geräusch eines Hundegebells hinzuzufügen. Dazu müssen Sie zwei Schaltflächen zur Auswahl des Geräuschs anzeigen – mit einer Ente und einem Hund. Lassen Sie uns zwei Schaltflächenklassen erstellen: DuckButton und DogButton.
Ändern Sie den Code:

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

class DogButton {
    func didPress() {
        print("bark!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

Nach einem weiteren Erfolg fügen wir den Klang eines Schweinequietschens hinzu, jetzt gibt es drei Klassen von Tasten:

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

class DogButton {
    func didPress() {
        print("bark!")
    }
}

class PigButton {
    func didPress() {
        print("oink!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

let pigButton = PigButton()
pigButton.didPress()

Benutzer beschweren sich darüber, dass sich die Geräusche überlappen.
Wir fügen eine Prüfung hinzu, um dies zu verhindern, und stellen gleichzeitig die Klassen einander vor:

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    func didPress() {
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

let pigButton = PigButton()
pigButton.didPress()

Aufgrund des Erfolgs Ihres Antrags beschließt die Regierung, ein Gesetz zu erlassen, nach dem das Quacksalbern, Bellen und Grunzen auf Mobilgeräten an den restlichen Wochentagen nur von 9:00 bis 15:00 Uhr erfolgen darf Zu diesem Zeitpunkt riskiert der Benutzer Ihrer Anwendung eine Gefängnisstrafe von 5 Jahren wegen obszöner Tonproduktion mit persönlichen elektronischen Geräten.
Ändern Sie den Code:

import Foundation

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    func didPress() {
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let duckButton = DuckButton()
let dogButton = DogButton()
let pigButton = PigButton()

duckButton.dogButton = dogButton
duckButton.pigButton = pigButton

dogButton.duckButton = duckButton
dogButton.pigButton = pigButton

pigButton.duckButton = duckButton
pigButton.dogButton = dogButton

duckButton.didPress()
dogButton.didPress()
pigButton.didPress()

Plötzlich fängt die Taschenlampen-Anwendung an, unsere vom Markt zu verdrängen. Lassen wir uns davon nicht unterkriegen und fügen wir eine Taschenlampe hinzu, indem wir die „oink-oink“-Taste und die restlichen Tasten drücken:

import Foundation

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

class Flashlight {

    var isOn = false

    func turn(on: Bool) {
        isOn = on
    }
}

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let flashlight = Flashlight()
let duckButton = DuckButton()
let dogButton = DogButton()
let pigButton = PigButton()

duckButton.dogButton = dogButton
duckButton.pigButton = pigButton
duckButton.flashlight = flashlight

dogButton.duckButton = duckButton
dogButton.pigButton = pigButton
dogButton.flashlight = flashlight

pigButton.duckButton = duckButton
pigButton.dogButton = dogButton
pigButton.flashlight = flashlight

duckButton.didPress()
dogButton.didPress()
pigButton.didPress()

Als Ergebnis haben wir eine riesige Anwendung, die viel Copy-Paste-Code enthält, die Klassen darin sind durch einen toten Link miteinander verbunden – es gibt keine schwache Kopplung, ein solches Wunder ist sehr schwer zu warten und Änderungen in der Zukunft aufgrund der hohen Wahrscheinlichkeit, dass ein Fehler gemacht wird.

Verwenden Sie den Mediator

Fügen wir eine mittlere Mediatorklasse hinzu – ApplicationController. Diese Klasse sorgt für eine lose Kopplung von Objekten, gewährleistet die Trennung der Verantwortlichkeiten zwischen den Klassen und eliminiert doppelten Code.
Schreiben wir um:

import Foundation

class ApplicationController {

    private var isMakingSound = false
    private let flashlight = Flashlight()
    private var soundButtons: [SoundButton] = []

    func add(soundButton: SoundButton) {
        soundButtons.append(soundButton)
    }
    
    func didPress(soundButton: SoundButton) {
        flashlight.turn(on: true)
        guard Date().mobileDeviceAllowedSoundTime() && 
                isMakingSound == false else { return }
        isMakingSound = true
        soundButton.didPress()
        isMakingSound = false
    }
}

class SoundButton {
    let soundText: String
    
    init(soundText: String) {
        self.soundText = soundText
    }
    
    func didPress() {
        print(soundText)
    }
}

class Flashlight {
    var isOn = false

    func turn(on: Bool) {
        isOn = on
    }
}

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

let applicationController = ApplicationController()
let pigButton = SoundButton(soundText: "oink!")
let dogButton = SoundButton(soundText: "bark!")
let duckButton = SoundButton(soundText: "quack!")

applicationController.add(soundButton: pigButton)
applicationController.add(soundButton: dogButton)
applicationController.add(soundButton: duckButton)

pigButton.didPress()
dogButton.didPress()
duckButton.didPress()

Viele Artikel über Anwendungsarchitekturen für Benutzeroberflächen beschreiben das MVC-Muster und seine Ableitungen. Das Modell wird für die Arbeit mit Geschäftslogikdaten verwendet. Die Ansicht oder Präsentation zeigt dem Benutzer Informationen in der Schnittstelle an bzw. sorgt für die Interaktion mit dem Benutzer. Der Controller ist ein Vermittler, der die Interaktion der Systemkomponenten sicherstellt.

Quellen

https://refactoring.guru/ru/design-patterns/ Vermittler

Quellcode

https://gitlab.com/demensdeum/patterns/< /p>

Strategy pattern

The Strategy pattern allows you to select the type of algorithm that implements a common interface, right while the application is running. This pattern refers to the behavioral design patterns.

Sun Tzu

Suppose we are developing a music player with embedded codecs. The built-in codecs imply reading music formats without using external sources of the operating system (codecs), the player should be able to read tracks of different formats and play them. VLC player has such capabilities, it supports various types of video and audio formats, it runs on popular and not very operating systems.

Imagine what a naive player implementation looks like:

var player: MusicPlayer?

func play(filePath: String) {
    let extension = filePath.pathExtension

    if extension == "mp3" {
        playMp3(filePath)
    }
    else if extension == "ogg" {
        playOgg(filePath)
    }
}

func playMp3(_ filePath: String) {
    player = MpegPlayer()
    player?.playMp3(filePath)
}

func playOgg(_ filePath: String) {
    player = VorbisPlayer()
    player?.playMusic(filePath)
}

Next, we add several formats, which leads to the need to write additional methods. Plus, the player must support plug-in libraries, with new audio formats that will appear later. There is a need to switch the music playback algorithm, the Strategy pattern is used to solve this problem.

Let’s create a common protocol MusicPlayerCodecAlgorithm, write the implementation of the protocol in two classes MpegMusicPlayerCodecAlgorithm and VorbisMusicPlayerCodecAlgorithm, to play mp3 and ogg files with-but. Create a class MusicPlayer, which will contain a reference for the algorithm that needs to be switched, then by the file extension we implement codec type switching:

import Foundation

class MusicPlayer {
    var playerCodecAlgorithm: MusicPlayerCodecAlgorithm?
    
	func play(_ filePath: String) {
            playerCodecAlgorithm?.play(filePath)
	}
}

protocol MusicPlayerCodecAlgorithm {
    func play(_ filePath: String)
}

class MpegMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("mpeg codec - play")
	}
}

class VorbisMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("vorbis codec - play")	
	}
}

func play(fileAtPath path: String) {
	guard let url = URL(string: path) else { return }
	let fileExtension = url.pathExtension
		
	let musicPlayer = MusicPlayer()
	var playerCodecAlgorithm: MusicPlayerCodecAlgorithm? 
		
	if fileExtension == "mp3" {
                playerCodecAlgorithm = MpegMusicPlayerCodecAlgorithm()
	}
	else if fileExtension == "ogg" {
                playerCodecAlgorithm = VorbisMusicPlayerCodecAlgorithm()
	}
		
	musicPlayer.playerCodecAlgorithm = playerCodecAlgorithm
	musicPlayer.playerCodecAlgorithm?.play(path)
}

play(fileAtPath: "Djentuggah.mp3")
play(fileAtPath: "Procrastinallica.ogg")

The above example also shows the simplest example of a factory (switching the codec type from the file extension) It is important to note that the Strategy strategy does not create objects, it only describes how to create a common interface for switching the family of algorithms.

Documentation

https://refactoring.guru/en/design-patterns/strategy

Source code

https://gitlab.com/demensdeum/patterns/

Iterator pattern

In this article I will describe the Iterator pattern.
This pattern refers to the behavioral design patterns.

Print it

Suppose we need to print a list of tracks from the album “Procrastinate them all” of the group “Procrastinallica”.
The naive implementation (Swift) looks like this:

for i=0; i < tracks.count; i++ {
    print(tracks[i].title)
}

Suddenly during compilation, it is detected that the class of the tracks object does not give the number of tracks in the count call, and moreover, its elements cannot be accessed by index. Oh…

Filter it

Suppose we are writing an article for the magazine “Wacky Hammer”, we need a list of tracks of the group “Djentuggah” in which bpm exceeds 140 beats per minute. An interesting feature of this group is that its records are stored in a huge collection of underground groups, not sorted by albums, or for any other grounds. Let’s imagine that we work with a language without functionality:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

Suddenly, a couple of tracks of the group are found in the collection of digitized tapes, and the editor of the magazine suggests finding tracks in this collection and writing about them. A Data Scientist friend suggests to use the Djentuggah track classification algorithm, so you don’t need to listen to a collection of 200 thousand tapes manually. Try:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

let tracksClassifier = TracksClassifier()
let bpmClassifier = BPMClassifier()

for track in cassetsTracks {
    if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

Mistakes

Now, just before sending to print, the editor reports that 140 beats per minute are out of fashion, people are more interested in 160, so the article should be rewritten by adding the necessary tracks.
Apply changes:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 160 {
        djentuggahFastTracks.append(track)
    }
}

let tracksClassifier = TracksClassifier()
let bpmClassifier = BPMClassifier()

for track in cassetsTracks {
    if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

The most attentive ones noticed an error; the bpm parameter was changed only for the first pass through the list. If there were more passes through the collections, then the chance of a mistake would be higher, that is why the DRY principle should be used. The above example can be developed further, for example, by adding the condition that you need to find several groups with different bpm, by the names of vocalists, guitarists, this will increase the chance of error due to duplication of code.

Behold the Iterator!

In the literature, an iterator is described as a combination of two protocols / interfaces, the first is an iterator interface consisting of two methods – next(), hasNext(), next() returns an object from the collection, and hasNext() reports that there is an object and the list is not over. However in practice, I observed iterators with one method – next(), when the list ended, null was returned from this object. The second is a collection that should have an interface that provides an iterator – the iterator() method, there are variations with the collection interface that returns an iterator in the initial position and in end – the begin() and end() methods are used in C ++ std.
Using the iterator in the example above will remove duplicate code, eliminate the chance of mistake due to duplicate filtering conditions. It will also be easier to work with the collection of tracks on a single interface – if you change the internal structure of the collection, the interface will remain old and the external code will not be affected.
Wow!

let bandFilter = Filter(key: "band", value: "Djentuggah")
let bpmFilter = Filter(key: "bpm", value: 140)
let iterator = tracksCollection.filterableIterator(filters: [bandFilter, bpmFilter])

while let track = iterator.next() {
    print("\(track.band) - \(track.title)")
}

Changes

While the iterator is running, the collection may change, thus causing the iterator’s internal counter to be invalid, and generally breaking such a thing as “next object”. Many frameworks contain a check for changing the state of the collection, and in case of changes they return an error / exception. Some implementations allow you to remove objects from the collection while the iterator is running, by providing the remove() method in the iterator.

Documentation

https://refactoring.guru/en/design-patterns/iterator

Source code

https://gitlab.com/demensdeum/patterns/

Muster „Schnappschuss“

In diesem Beitrag werde ich das Muster „Snapshot“ beschreiben. oder “Memento”

Dieses Muster bezieht sich auf „Verhaltensmuster“. Designmuster.

Angenommen, wir entwickeln einen Grafikeditor und müssen die Möglichkeit hinzufügen, Aktionen bei einem Benutzerbefehl rückgängig zu machen. Es ist auch sehr wichtig, dass die Systemkomponenten bei der Implementierung dieses Musters keinen Zugriff auf den internen Status der zurückgesetzten „Aktionen“ haben; andere Systemkomponenten haben nur Zugriff auf das Snapshot-Objekt, ohne die Möglichkeit, Änderungen vorzunehmen seinen internen Zustand und stellt eine klare, einfache externe Schnittstelle bereit. Um dieses Problem zu lösen, wird das „Snapshot“-Muster verwendet. oder “Keeper”.

Beispiel für die Arbeit „Snapshot“; unten dargestellt:


Wenn Sie darauf klicken, erscheint ein Sprite. Wenn Sie auf den gewellten Pfeil klicken, wird die Aktion abgebrochen – Der Sprite verschwindet. Das Beispiel besteht aus drei Klassen:

  1. Leinwand, auf der Sprites und die grafische Oberfläche angezeigt werden.
  2. Bildschirm-Controller, er verarbeitet Klicks und steuert die Logik des Bildschirms.
  3. Canvas-Zustände, die bei jeder Änderung bestehen bleiben, werden bei Bedarf mithilfe des Bildschirm-Controllers zurückgesetzt.

Im Kontext des Musters “Snapshot” Klassen sind:

  1. Leinwand – Quelle: Die Zustände dieser Klasse werden als „Schnappschüsse“ gespeichert, für ein späteres Rollback auf Anfrage. Außerdem muss die Quelle in der Lage sein, den Status wiederherzustellen, wenn ein „Snapshot“ an sie übertragen wird.
  2. Controller – Depotbank, diese Klasse weiß, wie und wann Zustände gespeichert/zurückgesetzt werden müssen.
  3. Status – Snapshot, eine Klasse, die den Status der Quelle sowie Datumsinformationen oder einen Index speichert, anhand dessen die Rollback-Reihenfolge genau festgelegt werden kann.

Ein wichtiges Merkmal des Musters ist, dass nur die Quelle Zugriff auf die internen Felder des gespeicherten Zustands im Snapshot haben sollte. Dies ist notwendig, um Snapshots vor Änderungen von außen zu schützen (durch geschickte Entwickler, die unter Umgehung der Kapselung etwas ändern möchten). , die Systemlogik brechen). Um die Kapselung zu implementieren, werden integrierte Klassen verwendet, und in C++ nutzen sie die Möglichkeit, Freundklassen anzugeben. Persönlich habe ich eine einfache Version ohne Kapselung für Rise implementiert und bei der Implementierung für Swift Generic verwendet. In meiner Version – Memento gibt seinen internen Status nur an Entitäten desselben Klassenstatus weiter:

Quellen

https://refactoring.guru/design-patterns/memento

Quellcode

https://gitlab.com/demensdeum/patterns/< /p>

Besuchermuster

In diesem Beitrag beschreibe ich ein Designmuster namens „Besucher“. oder „Besucher“
Dieses Muster gehört zur Gruppe der Verhaltensmuster.

Lass uns ein Problem finden

Dieses Muster wird hauptsächlich verwendet, um die Beschränkung des Einzelversands in Sprachen mit früher Bindung zu umgehen.

Alice X von NFGPhoto (CC-2.0)
Lassen Sie uns eine abstrakte Klasse/ein abstraktes Protokoll Band erstellen, eine Unterklasse von MurpleDeep erstellen und eine Besucherklasse mit zwei Methoden erstellen – Eine für die Ausgabe aller Nachkommen von Band an die Konsole, die zweite für die Ausgabe von MurpleDeep. Hauptsache, die Namen (Signaturen) der Methoden sind gleich und die Argumente unterscheiden sich nur je nach Klasse. Mithilfe der Zwischenausdruckmethode mit dem Band-Argument erstellen wir eine Instanz von Visitor und rufen die Visit-Methode für MurpleDeep auf.
Unten ist der Code in Kotlin:

Die Ausgabe lautet: „Dies ist die Band-Klasse

Wie ist das möglich?!

Warum das passiert, wird in vielen Artikeln mit klugen Worten beschrieben, auch auf Russisch, aber ich schlage vor, Sie stellen sich vor, wie der Compiler den Code sieht, vielleicht wird alles sofort klar:

Das Problem lösen

Es gibt viele Lösungen, um dieses Problem zu lösen. Als nächstes betrachten wir eine Lösung, die das Besuchermuster verwendet.
Wir fügen der abstrakten Klasse/dem abstrakten Protokoll die Methode „accept“ mit dem Argument „Visitor“ hinzu, rufen „visitator.visit(this)“ innerhalb der Methode auf und fügen dann eine Überschreibung/Implementierung der Methode „accept“ zur Klasse „MurpleDeep“ hinzu, wodurch wir wiederum entschieden und ruhig gegen DRY verstoßen Besucher.visit(this).< br />Endgültiger Code:

Quellen

https://refactoring.guru/ru/ design-patterns/visitor-double-dispatch

Quellcode

https://gitlab.com/demensdeum/patterns

Fliegengewichtsmuster

In diesem Beitrag werde ich das Strukturmuster „Lightweight“ beschreiben. oder „opportunistisch“; (Fliegengewicht)
Dieses Muster gehört zur Gruppe der Strukturmuster

Sehen wir uns unten ein Beispiel an, wie das Muster funktioniert:


Warum wird es benötigt? Um RAM zu sparen. Ich stimme zu, dass dies in Zeiten der weit verbreiteten Verwendung von Java (das umsonst CPU und Speicher verbraucht) nicht mehr so ​​wichtig ist, aber es lohnt sich, es zu verwenden.
Im obigen Beispiel werden nur 40 Objekte ausgegeben, aber wenn Sie die Anzahl auf 120.000 erhöhen, erhöht sich der Speicherverbrauch entsprechend.
Schauen wir uns den Speicherverbrauch an, ohne das Flyweight-Muster im Chromium-Browser zu verwenden:

Ohne Verwendung eines Musters beträgt der Speicherverbrauch etwa 300 Megabyte.

Jetzt fügen wir der Anwendung ein Muster hinzu und sehen uns den Speicherverbrauch an:

Bei Verwendung des Musters beträgt der Speicherverbrauch ~200 Megabyte, sodass wir in der Testanwendung 100 Megabyte Speicher eingespart haben. Bei ernsthaften Projekten kann der Unterschied viel größer sein.

Wie funktioniert es?

Im obigen Beispiel zeichnen wir 40 Katzen, der Übersichtlichkeit halber also 120.000. Jede Katze wird als PNG-Bild in den Speicher geladen und dann in den meisten Renderings zum Rendern in eine Bitmap (eigentlich BMP) konvertiert. Dies geschieht aus Geschwindigkeitsgründen, da das Rendern eines komprimierten PNG sehr lange dauert. Ohne das Muster zu verwenden, laden wir 120.000 Bilder von Katzen in den RAM und zeichnen, aber wenn wir das Muster „leichtgewichtig“ verwenden, können wir es nicht verwenden. Wir laden eine Katze in den Speicher und zeichnen sie 120.000 Mal mit unterschiedlichen Positionen und unterschiedlicher Transparenz. Die ganze Magie besteht darin, dass wir beim Rendern Koordinaten und Transparenz getrennt vom Katzenbild implementieren. Das Rendern benötigt nur eine Katze und verwendet ein Objekt mit Koordinaten und Transparenz für das korrekte Rendern.

Wie sieht es im Code aus?

Im Folgenden finden Sie Beispiele für die Sprache Rise< /p>

Ohne Verwendung eines Musters:


Das Katzenbild wird für jedes Objekt in der Schleife separat geladen – catImage.

Muster verwenden:

Ein Bild einer Katze wird von 120.000 Objekten verwendet.

Wo wird es verwendet?

Wird in GUI-Frameworks verwendet, zum Beispiel Apples “Wiederverwendung” (Wiederverwendung) von UITableViewCell-Tabellenzellen, was die Einstiegshürde für Anfänger erhöht, die dieses Muster nicht kennen. Wird auch häufig in der Spieleentwicklung verwendet.

Quellcode

https://gitlab.com/demensdeum/patterns/< /p>

Quellen

https://refactoring.guru/ru/design-patterns/ Fliegengewicht
http://gameprogrammingpatterns.com/flyweight.html

С++ Application Plugins

In this post I will describe an example of adding functionality to a C ++ application using plugins. The practical part of the implementation for Linux is described; the theory can be found at the links at the end of the article.

Composition over inheritance!

To begin with, we will write a plugin – a function that we will call:

#include "iostream"

using namespace std;

extern "C" void extensionEntryPoint() {
	cout << "Extension entry point called" << endl;
};

Next, we will build the plugin as a dynamic library “extension.so”, which we will connect in the future:
clang++ -shared -fPIC extension.cpp -o extension.so

Next we write the main application that will load the file “extension.so”, look for a pointer to the function “extensionEntryPoint” there, and call it, typing errors if necessary:

#include "iostream"
#include "dlfcn.h"

using namespace std;

typedef void (*VoidFunctionPointer)();	

int main (int argc, char *argv[]) {

	cout << "C++ Plugins Example" << endl;

	auto extensionHandle = dlopen("./extension.so", RTLD_LAZY);
	if (!extensionHandle) {
		string errorString = dlerror();
		throw runtime_error(errorString);
	}

	auto functionPointer = VoidFunctionPointer();
	functionPointer = (VoidFunctionPointer) dlsym(extensionHandle, "extensionEntryPoint");
	auto dlsymError = dlerror();
 	if (dlsymError) {
		string errorString = dlerror();
		throw runtime_error(errorString);
 	}

	functionPointer();

	exit(0);
} 

The dlopen function returns a handler for working with a dynamic library; dlsym function returns a pointer to the required function by string; dlerror contains a pointer to the string with the error text, if any.

Next, build the main application, copy the file of the dynamic library in the folder with it and run. The output should be the “Extension entry point called”

Difficult moments include the lack of a single standard for working with dynamic libraries, because of this there is a need to export the function to a relatively global scope with extern C; the difference in working with different operating systems associated with this subtlety of work; the lack of a C ++ interface to implement OOP approach to working with dynamic libraries, however, there are open-source wrappers, for example m-renaud/libdlibxx

Example Source Code

https://gitlab.com/demensdeum/cpppluginsexample

Documents

http://man7.org/linux/man-pages/man3/dlopen.3.htm
https://gist.github.com/tailriver/30bf0c943325330b7b6a
https://stackoverflow.com/questions/840501/how-do-function-pointers-in-c-work

Flattern wie Michelle

[Spüren Sie die Kraft der künstlichen Intelligenz]
In diesem Beitrag werde ich Ihnen sagen, wie Sie die Zukunft vorhersagen.

In der Statistik gibt es eine Klasse von Problemen – Zeitreihenanalyse. Wenn Sie ein Datum und den Wert einer bestimmten Variablen haben, können Sie den Wert dieser Variablen in der Zukunft vorhersagen.
Zuerst wollte ich eine Lösung für dieses Problem implementieren, indem ich TensorFlow, aber die Bibliothek Prophet von Facebook.
Prophet ermöglicht es Ihnen, eine Prognose basierend auf Daten (csv) zu erstellen, die Datumsspalten (ds) und Variablenwertspalten (y) enthalten. Wie man damit arbeitet, erfahren Sie in der Dokumentation auf der offiziellen Website im Abschnitt Schnellstart
Als Datensatz habe ich einen CSV-Upload von der Website https://www.investing.com, bei der Implementierung habe ich R-Sprache und Prophet-API für ihn. Mir hat R sehr gut gefallen, weil seine Syntax die Arbeit mit großen Datenmengen vereinfacht, ein einfacheres Schreiben ermöglicht und weniger Fehler macht als bei der Arbeit mit herkömmlichen Sprachen (Python), da man mit Lambda-Ausdrücken arbeiten müsste, und in R haben Sie bereits alle Lambda-Ausdrücke.
Um die Daten nicht für die Verarbeitung vorzubereiten, habe ich das Paket anytime, das Zeichenfolgen ohne Vorverarbeitung in ein Datum konvertieren kann. Die Konvertierung von Währungszeichenfolgen in Zahlen erfolgt mit dem readr-Paket .

Als Ergebnis erhielt ich eine Prognose, wonach Bitcoin bis Ende 2019 8.400 US-Dollar kosten wird und der Dollarkurs 61 Rubel betragen wird. Sollten wir diesen Prognosen Glauben schenken? Persönlich denke ich, dass es sich nicht lohnt, weil… Man kann mathematische Methoden nicht anwenden, ohne ihr Wesen zu verstehen.

Quellen

https:// facebook.github.io/prophet
https://habr.com/company/ods/blog/323730/
https://www.r-project.org/

Quellcode

https://gitlab.com/demensdeum/MachineLearning/tree/master/4prophet

Tesla spricht

In diesem Beitrag beschreibe ich den Prozess der Erstellung eines Zitatgenerators.

TL;DR

Für Schulung und Textgenerierung – Verwenden Sie die Bibliothek textgenrnn, um Phrasen zu filtern, müssen Sie die Rechtschreibprüfung mit hunspell und seine Bibliotheken für C/Python. Nach dem Training in Colaboratory, Sie können mit der Textgenerierung beginnen. Etwa 90 % des Textes werden erstellt völlig unlesbar, die restlichen 10 % enthalten jedoch ein wenig Bedeutung, und mit manueller Änderung sehen die Phrasen recht gut aus.
Am einfachsten ist es, ein vorgefertigtes neuronales Netzwerk in Colaboratory zu starten:
https://colab.research.google.com/drive/1-wbZMmxvsm3SoclJv11villo9VbUesbc(öffnet sich in einem neuen Tab)”>https://colab.research.google.com/drive/1-wbZMmxvsm3SoclJv11villo9VbUesbc

Quellcode

https://gitlab.com/demensdeum/MachineLearning/tree/master/3quotesGenerator

Quellen

https://karpathy.github.io/2015/05/21/rnn-effectiveness/https://medium.com/deep-learning-turkey/google-colab-free-gpu-tutorial-e113627b9f5dhttps://minimaxir.com/2018/05/text-neural-networks/
https://github.com/wooorm/dictionaries (opens in a new tab)” href=”https://minimaxir.com/2018/05/text-neural-networks/” target=”_blank”>https://minimaxir.com/2018/05/text-neural-networks/
https://karpathy.github.io/2015/05/21/rnn-effectiveness/
https://medium.com/deep-learning-turkey/google-colab-free-gpu-tutorial-e113627b9f5d
https://karpathy.github.io/2015/05/21/rnn-effectiveness/ (opens in a new tab)” href=”https://karpathy.github.io/2015/05/21/rnn-effectiveness/” target=”_blank”>https://karpathy.github.io/2015/05/21/rnn-effectiveness/
https://medium.com/deep-learning-turkey/google-colab-free-gpu-tutorial-e113627b9f5d
https://karpathy.github.io/2015/05/21/rnn-effectiveness/https://medium.com/deep-learning-turkey/google-colab-free-gpu-tutorial-e113627b9f5dhttps://github.com/wooorm/dictionaries

” rel=”noopener” target=”_blank”>https://github.com/wooorm/dictionaries (opens in a new tab)”>https://github.com/wooorm/dictionaries

Wie viele Fehler hast du da?

Auf Hacker News habe ich einen sehr interessanten Artikel gefunden, in dem der Autor die Verwendung von vorschlägt Petersen-Lincoln-Methode, die von Biologen verwendet wird, um die Population von Vögeln, Affen und anderen Tieren zu zählen, zum *Trommelwirbel* Zählen von Käfern in der Anwendung.

Käfer im natürlichen Lebensraum – Bigfoot-Sichtung von Derek Hatfield

Die Methode ist sehr einfach, wir nehmen zwei Ornithologen, finden sie Vögel einer bestimmten Art, ihre Aufgabe – Bestimmen Sie die Populationsgröße dieser Vögel. Die gefundenen Vögel werden von beiden Ornithologen markiert, dann wird die Anzahl der häufigen Vögel berechnet, in die Lincoln-Indexformel eingesetzt und wir erhalten die ungefähre Populationsgröße.
Nun zu den Bewerbungen – Die Methode ist auch sehr einfach: Wir nehmen zwei Qualitätssicherungskräfte und sie finden Fehler in der Anwendung. Nehmen wir an, ein Tester hat 10 Fehler gefunden (E1) und der zweite Tester hat 20 Fehler gefunden (E2). Jetzt nehmen wir die Anzahl der Gesamtfehler – 3 (S), dann erhalten wir mit der Formel den Lincoln-Index:

Dies ist eine Prognose der Anzahl der Fehler in der gesamten Anwendung. Im angegebenen Beispiel sind es etwa 66 Fehler.

Schnelles Beispiel

Ich habe einen Prüfstand implementiert, um die Methode zu testen, Sie können ihn hier sehen:
https://paiza.io/projects/AY_9T3oaN9a-xICAx_H4qw?language=swift

Parameter, die geändert werden können:

let aliceErrorFindProbability = 20 – Prozentsatz der von QA Alice gefundenen Fehler (20 %)
let bobErrorFindProbability = 60 – Prozentsatz der von QA Bob gefundenen Fehler (60 %)
let currentBugsCount = 200 – Wie viele Fehler gibt es wirklich in der Anwendung?

Im letzten Durchlauf habe ich folgende Daten erhalten:
Anzahl der Schätzungsfehler: 213
Tatsächliche Anzahl der Fehler: 200

Das heißt, es gibt 200 Fehler in der Anwendung, der Lincoln Index gibt eine Prognose – 213:
„Alice hat 36 Fehler gefunden.“
„Bob hat 89 Fehler gefunden.“
“Anzahl häufiger Fehler: 15”

Anzahl der Schätzungsfehler: 213
Tatsächliche Anzahl der Fehler: 200

Schwächen

Diese Methode kann verwendet werden, um die Anzahl der Fehler in der Anwendung in allen Phasen der Entwicklung zu bewerten. Im Idealfall sollte die Anzahl der Fehler sinken. Zu den Schwächen der Methode gehört unter anderem der menschliche Faktor, da die Anzahl der von zwei Testern gefundenen Fehler unterschiedlich sein sollte und unterschiedliche Fehler gefunden wurden häufige Fehler müssen gefunden werden, andernfalls funktioniert die Methode nicht (null häufige Fehler – Division durch Null)< br />Außerdem erfordert ein Konzept wie häufige Fehler die Anwesenheit eines Experten, um ihre Gemeinsamkeiten zu verstehen.

Quellen

Wie viele Fehler müssen noch gefunden werden? – John D. Cook, PhD, Präsident
The thrill of the chase – Brian Hayes

Quellcode

https://paiza.io/projects/AY_9T3oaN9a-xICAx_H4qw ?Sprache=swift
https://gitlab.com/demensdeum/statistics/tree/master/1_BugsCountEstimation/src

Wir haben Malevich, Black Squares OpenGL geschlagen

Malevich kommt regelmäßig jedem Entwickler auf OpenGL. Dies geschieht unerwartet und mutig.

Heute werde ich beschreiben, aus welchem Grund ich von einem schwarzen Quadrat besucht wurde.

Verwenden Sie Tools

Für das Debuggen von OpenGL haben mir zwei Tools geholfen: renderdoc und und Apitrace . Renderdoc – Tool zum Debuggen des OpenGL -Rendering -Prozesses können Sie alles anzeigen – Scheitelpunkte, Shader, Texturen, Schuldennachrichten des Fahrers. Apitrace – Ein Werkzeug für die Verfolgung von Herausforderungen einer grafischen API macht einen Dump -Anruf und zeigt Argumente an. Es gibt auch eine großartige Gelegenheit, zwei Dumps über WDIFF (oder ohne, aber nicht so bequem) zu vergleichen

Überprüfen Sie, mit wem Sie arbeiten

Ich habe ein Betriebssystem Ubuntu 16.10 mit alten Abhängigkeiten SDL2, GLM, Anlage, Glew. In der neuesten Version von Ubuntu 18.04 erhalte ich die Zusammenstellung des Spiels Todesmaske Das zeigt nichts auf dem Bildschirm (nur ein Black Square). Wenn Sie Chroot und Assembly bei 16.10 i verwenden, erhalte ich eine Arbeitsanordnung des Spiels mit Graphics .

In Ubuntu 18.04

scheint etwas gebrochen zu sein

ldd zeigte die Linkka zu identischen Bibliotheken SDL2, GL. Als ich einen nicht bearbeiteten Build in Renderdoc fuhr, sah ich Müll am Eingang zum Scheitelpunkt -Shader, aber ich brauchte eine solide Bestätigung. Um den Unterschied zwischen der Binarik zu verstehen, habe ich sie beide durch apitrace gefahren. Der Vergleich von Dumps hat mir gezeigt, dass die Versammlung auf einem frischen Ubunta das Programm der Aussichten in OpenGL bricht und tatsächlich Müll dorthin schickt:

Matrizen sammeln sich in der GLM -Bibliothek. Nach dem Kopieren von GLM von 16.04 – Ich habe wieder den Arbeitsaufbau des Spiels bekommen. Das Problem war der Unterschied in der Initialisierung einer einzelnen Matrix in GLM 9.9.0, es ist notwendig, das Argument MAT4 (1.0F) im Konstruktor eindeutig anzuzeigen. Nachdem ich die Initialisierung und geändert habe, indem ich der Autor der Bibliothek abgeschrieben habe. “Noopener”> Tests für FSGL . Beim Schreiben, den ich in FSGL fand, werde ich sie weiter beschreiben.

Bestimmen Sie, wer im Leben ist

Für die richtige Arbeit mit OpenGL müssen Sie freiwillig gewaltsam den Kontext einer bestimmten Version anfordern. So sucht es nach SDL2 (Sie müssen die Version streng einsetzen, bevor Sie den Kontext initialisieren):


 SDL_GL_SETTRTRIBUT (SDL_GL_CONTEXT_MAJOR_VERSION,  3 );
Sdl_gl_settribute (sdl_gl_context_minor_version, 2 );
Sdl_gl_settribute (sdl_gl_context_profile_mask, sdl_gl_context_profile_core);

zum Beispiel funktioniert Renderdoc nicht mit Kontexten unter 3.2. Ich möchte feststellen, dass nach dem Umschalten des Kontextes eine hohe Wahrscheinlichkeit besteht, dass der gleiche schwarze Bildschirm angezeigt wird. Warum?
Denn der -Kontext von OpenGL 3.2 muss das Vorhandensein von VAO -Puffer erfordern, ohne dass 99% der grafischen Treiber nicht funktionieren. Fügen Sie es einfach hinzu:


 GlGenvertexArrays ( 1 ,  &  vao);
Glbindvertexaray (vao);

schlaf nicht, friere ein.

Ich habe auch ein interessantes Problem auf Kubuntu getroffen, anstatt auf ein schwarzes Quadrat wurde mir transparent angezeigt, und Manchmal wurde alles richtig gemacht. Ich fand die Lösung für dieses Problem beim Stack -Überlauf:
https://stackoverflow.com/questions/38411515/sdl2-opengl-window-appears-semi-transparent-sometimes

Der FSGL -Test -Render -Code war ebenfalls vorhanden Schlaf (2S) ; Also erhielt ich auf dem Xubuntu und Ubuntu das richtige Rendern und schickte den Antrag in den Schlaf, aber auf Kubuntu erhielt ich einen transparenten Bildschirm in 80% des Starts von Delphin und 30% der Starts und Terminal. Um dieses Problem zu lösen, fügte ich nach einer Sdlevent -Umfrage in jedem Frame Rendering hinzu, wie in der Dokumentation empfohlen.

Testcode:
https://gitlab.com/demensdeum/FSGLtests/blob/master/renderModelTest/

Sprechen Sie mit dem Treiber

OpenGL unterstützt den Kommunikationskanal zwischen der Anwendung und dem Treiber, um ihn zu aktivieren. .
Hier kann ein Beispiel für die Initialisierung aufgenommen werden:
https://github.com/rock-core/gui-vizkit3d/blob/master/src/EnableGLDebugOperation.cpp

Hab keine Angst, sieh zu, wie es wächst

In diesem Beitrag werde ich über meine Missgeschicke mit shared_ptr Smart Pointern sprechen. Nachdem ich die Next-Level-Generation in meinem Spiel Death-Mask implementiert hatte, bemerkte ich eine Erinnerung Leck . Jede neue Stufe erhöhte den verbrauchten RAM um + 1 Megabyte. Es ist offensichtlich, dass einige Objekte im Speicher verblieben und nicht freigegeben wurden. Um diesen Umstand zu korrigieren, war es notwendig, die korrekte Implementierung von Ressourcen bei Überlastung des Levels zu implementieren, was offenbar nicht geschehen ist. Da ich intelligente Zeiger verwendet habe, gab es mehrere Möglichkeiten, dieses Problem zu lösen. Die erste bestand darin, den Code manuell zu überprüfen (lang und langweilig), während die zweite darin bestand, die Fähigkeiten des lldb-Debuggers und des libstdc++-Quellcodes auf die Möglichkeit einer automatischen Nachverfolgung zu untersuchen Zähleränderungen.

Im Internet liefen alle Ratschläge darauf hinaus, den Code manuell zu überprüfen, ihn zu reparieren und sich selbst mit Peitschenhieben zu schlagen, nachdem man die problematische Codezeile gefunden hatte. Es wurde auch vorgeschlagen, ein eigenes System für die Arbeit mit dem Speicher zu implementieren, wie es alle großen Projekte tun, die seit den 90er und 2000er Jahren entwickelt wurden, bevor intelligente Zeiger im C++11-Standard eingeführt wurden. Ich habe versucht, Haltepunkte im Konstruktor einer Kopie aller shared_ptrs zu verwenden, aber nach mehreren Tagen passierte nichts Nützliches. Es gab die Idee, Protokollierung zur libstdc++-Bibliothek hinzuzufügen, aber der Arbeitsaufwand erwies sich als enorm.


Cowboy Bebop (1998)

Die Lösung kam mir plötzlich in Form der Verfolgung von Änderungen in der privaten Variablen shared_ptr – use_count. Dies kann mithilfe von in lldb integrierten Watchpoints erfolgen. Nach dem Erstellen eines shared_ptr über make_shared können Änderungen am Zähler in lldb mithilfe der Zeile:
verfolgt werden

watch set var camera._M_refcount._M_pi->_M_use_count

Wo “Kamera” Dies ist ein shared_ptr-Objekt, dessen Zählerstand verfolgt werden muss. Natürlich variieren die Interna von shared_ptr je nach Version von libstdc++, aber das allgemeine Prinzip ist verständlich. Nach der Installation des Watchpoints starten wir die Anwendungen und lesen den Stacktrace jeder Zähleränderung, dann schauen wir uns den Code an (sic!), finden das Problem und beheben es. In meinem Fall wurden Objekte nicht aus Cache-Tabellen und Spiellogiktabellen befreit. Ich hoffe, dass diese Methode Ihnen beim Umgang mit Lecks bei der Arbeit mit shared_ptr hilft, und ich liebe dieses Speichertool noch mehr. Viel Spaß beim Debuggen.

Einfaches TensorFlow-Beispiel

Ich präsentiere Ihnen ein einfaches Beispiel für die Arbeit mit einem Framework für die Arbeit mit Deep Learning – TensorFlow. In diesem Beispiel bringen wir einem neuronalen Netzwerk bei, positive, negative Zahlen und Nullen zu erkennen. Installation von TensorFlow und CUDA Ich sage Ihnen, diese Aufgabe ist wirklich nicht einfach)

Um Klassifizierungsprobleme zu lösen, Klassifikatoren. TensorFlow verfügt über mehrere vorgefertigte High-Level-Klassifikatoren, für deren Funktion nur eine minimale Konfiguration erforderlich ist. Zuerst trainieren wir DNNClassifier mit Datensatz mit positiven, negativen Zahlen und Null – mit den richtigen „Beschriftungen“. Auf menschlicher Ebene ist ein Datensatz eine Reihe von Zahlen mit Klassifizierungsergebnissen (Beschriftungen):

10 – positiv
-22 – negativ
0 – Null
42 – positiv
… andere Zahlen mit Klassifizierung

Als nächstes beginnt das Training. Anschließend können Sie Zahlen eingeben, die noch nicht einmal im Datensatz enthalten waren – Das neuronale Netzwerk muss sie korrekt identifizieren.
Nachfolgend finden Sie den vollständigen Code des Klassifikators mit einem Datensatzgenerator für Trainings- und Eingabedaten:

import tensorflowImport itertoolsImport Zufälligvon Zeit Import ZeitKlasse ClassifiedNumber:__number = 0__classifiedAs = 3def __init__(selbst, Zahl):self.__number =Nummerif Zahl == 0:self.__classifiedAs = 0 # Nullelif Zahl > 0:self.__classifiedAs = 1 # positivelif Zahl < 0:self.__classifiedAs = 2 # negativdef number(selbst):return self.__Nummerdef classifiedAs(selbst):return self.__classifiedAsdef classifiedAsString(classifiedAs):if klassifiziert als == 0:Zurück "Null"elif klassifiziert als == 1:Zurück "Positiv"elif klassifiziert als == 2:Zurück "Negativ"def trainDatasetFunction():trainNumbers = []trainNumberLabels = []für i im Bereich(-1000, 1001):Zahl = ClassifiedNumber(i)trainNumbers.append(number.number())trainNumberLabels.append(number.classifiedAs())return ( {"number" : trainNumbers } , trainNumberLabels)def inputDatasetFunction():global randomSeedrandom.seed(randomSeed) # um das gleiche Ergebnis zu erhaltenZahlen = []für i im range(0, 4):Zahlen.append(random.randint(-9999999, 9999999))Zurück {"number" : Zahlen }def main():print("TensorFlow Positiv-Negativ-Null-Zahlenklassifikatortest von demensdeum 2017 (demensdeum@gmail. com)")maximalClassesCount = len(set< /span>(trainDatasetFunction()[1])) + 1numberFeature = tensorflow.feature_column. numeric_column("number")Klassifikator = tensorflow.estimator. DNNClassifier(feature_columns = [numberFeature], versteckte_Einheiten = [10, 20, 10], n_classes = maximaleClassesCount)Generator = Klassifikator.train(input_fn = trainDatasetFunction, Schritte = 1000).predict(input_fn =  inputDatasetFunction)inputDataset = inputDatasetFunction()Ergebnisse = list(itertools. islice(generator, len(inputDatasetFunction()["number"])))i = 0für Ergebnis in Ergebnisse:print("number: %d klassifiziert als %s" % (inputDataset["number"][i], klassifiziertAsString(result["class_ids"][0 ])))i += 1randomSeed = time()hauptsächlich()

Alles beginnt mit der main()-Methode. Wir legen die numerische Spalte fest, mit der der Klassifikator arbeiten soll – tensorflow.feature_column.numeric_column(“number”) Als nächstes werden die Klassifikatorparameter festgelegt. Es ist sinnlos, die aktuellen Initialisierungsargumente zu beschreiben, da sich die API täglich ändert, und Sie sollten sich unbedingt die Dokumentation für die installierte Version von TensorFlow ansehen und sich nicht auf veraltete Handbücher verlassen.

Als nächstes wird das Training gestartet, das eine Funktion angibt, die einen Datensatz mit Zahlen von -1000 bis 1000 zurückgibt (trainDatasetFunction), mit der korrekten Klassifizierung dieser Zahlen basierend auf positiv, negativ oder Null. Als nächstes übermitteln wir als Eingabe Zahlen, die nicht im Trainingsdatensatz enthalten waren – zufällig von -9999999 bis 9999999 (inputDatasetFunction), um sie zu klassifizieren.

Schließlich starten wir Iterationen basierend auf der Anzahl der Eingabedaten (itertools.islice), drucken das Ergebnis aus, führen es aus und lassen uns überraschen:

number: 4063470 als positiv klassifiziertNummer: 6006715 als positiv eingestuftNummer: -5367127 als negativ eingestuftNummer: -7834276 als negativ eingestuft

iT’S LEBEND

Um ehrlich zu sein, bin ich immer noch ein wenig überrascht, dass der Klassifikator sogar die Zahlen *versteht*, die ich ihm nicht beigebracht habe. Ich hoffe, dass ich das Thema maschinelles Lernen in Zukunft besser verstehen werde und es weitere Tutorials geben wird.

GitLab:
https://gitlab.com/demensdeum/MachineLearning

Links:
https://developers.googleblog.com/2017/09/introducing-tensorflow-datasets.html
https://www.tensorflow.org/versions/master/api_docs/python/tf/estimator/DNNClassifier

Bitcoin brechen

Dieser Hinweis ist kein Aufruf zum Handeln; ich werde hier die schwachen und potenziell gefährlichen Aspekte der Bitcoin- und Blockchain-Technologie beschreiben.

Gefährdetes Zentrum

Das Funktionsprinzip von Bitcoin und Blockchain besteht darin, eine gemeinsame Datenbank zu speichern und zu ändern, von der jeder Netzwerkteilnehmer eine vollständige Kopie speichert. Das System sieht dezentral aus, weil… Es gibt keine einzelne Organisation/Server, auf der die Datenbank gespeichert ist. Außerdem wird die Dezentralisierung als Hauptvorteil der Blockchain dargestellt; sie garantiert, dass Ihren Bitcoins nichts ohne Ihr Wissen passiert.


Das Block-Pest-Prinzip von Elkin

Damit die Blockchain funktioniert, muss sichergestellt werden, dass jeder Benutzer die neueste Kopie der Blockchain-Datenbank herunterlädt und nach bestimmten Regeln damit arbeitet. Zu diesen Regeln gehört die Umsetzung des Bitcoin-Mining-Prinzips, bei dem ein Prozentsatz jeder Transaktion nach Bestätigung (Transaktionsgebühr) des Geldtransfers von einer Wallet zur anderen erhalten wird. Der Benutzer kann nicht 1.000.000 Bitcoins für sich ziehen und damit etwas kaufen, weil… Für andere Benutzer bleibt der Geldbetrag auf seinem Konto unverändert. Ausgeschlossen ist auch die Möglichkeit, Gelder nur innerhalb der eigenen Datenbank von der Wallet einer anderen Person abzuheben, weil Diese Änderung wird nicht auf andere Bitcoin-Benutzer übertragen und ignoriert.
Die Schwachstelle der aktuellen Implementierung besteht darin, dass sich das Bitcoin-Wallet auf dem Server github befindet blockiert Werbeslogans zur Dezentralisierung vollständig. Kein Wallet-Download von einem einzigen Zentrum – Auf der Website des Entwicklers ist es unmöglich, mit Bitcoin zu arbeiten, das heißt, die Entwickler haben jederzeit die vollständige Kontrolle über das Netzwerk. Somit ist die Blockchain-Technologie selbst dezentral, aber der Client für die Arbeit mit dem Netzwerk wird von einem einzigen Zentrum.
Angriffsszenario – Nehmen wir an, dem Wallet wurde ein Code hinzugefügt, um alle Gelder abzuheben und auf das Konto Dritter auszuzahlen. Danach verliert jeder Benutzer der neuesten Version des Wallets automatisch alle Bitcoins (ohne Möglichkeit der Wiederherstellung). Ich bezweifle, dass viele Wallet-Besitzer es überprüfen und aus dem Quellcode erstellen, sodass die Folgen eines solchen Angriffs die meisten Benutzer betreffen werden.

Mehrheit entscheidet

Blockchain ist ein dezentrales P2P-Netzwerk; alle Transaktionen werden von den Benutzern selbst automatisch bestätigt. Angriffsszenario – Es ist notwendig, 51 % des Netzwerks zu erlangen, um Bestätigungen der restlichen 49 % zu ignorieren, woraufhin der Angreifer die volle Kontrolle über Bitcoin/Blockchain erlangt. Dies kann durch die Verbindung von Rechenleistung erreicht werden, die sich mit dem Rest überschneidet. Dieses Angriffsszenario ist als 51 %-Angriff bekannt.

Erraten Sie mich, wenn Sie können

Wenn Sie das Wallet zum ersten Mal starten, generiert der Computer ein Paar – privaten und öffentlichen Schlüssel, um den korrekten Betrieb sicherzustellen. Die Einzigartigkeit dieser Schlüssel ist extrem hoch, es besteht jedoch die Möglichkeit, Schlüssel mithilfe des Codeworts – das sogenannte „Brain Wallet“. Eine Person speichert Schlüssel in ihrem Kopf; sie muss die Datei wallet.dat nicht sichern, weil Mit diesem Codewort können die Schlüssel jederzeit neu generiert werden. Angriffsszenario – Der Angreifer wählt oder lernt das Codewort, generiert ein privates-öffentliches Schlüsselpaar und erlangt die Kontrolle über die Wallet.

Einfach kopieren

Das private-öffentliche Schlüsselpaar ist in der Datei wallet.dat enthalten. Jede Software, die Zugriff auf diese Datei hat – hat Zugriff auf eine Bitcoin-Wallet. Der Schutz vor einem solchen Angriff besteht in der Hinzufügung eines Codeworts, das sich der Benutzer merken und bei allen Transaktionen mit dem Wallet eingeben muss. Nach dem Hinzufügen des Codeworts benötigt der Angreifer wallet.dat und das Codewort, um die volle Kontrolle zu erlangen.
Es ist auch erwähnenswert, dass bei der Eingabe eines Codeworts dieses im Speicher des Computers gespeichert wird. Daher können alle Hardware- und/oder Software-Schwachstellen, die das Auslesen des Speichers *anderer* ermöglichen, dazu führen, dass Virensoftware dieses Codewort lesen kann.

Systemfehler

Das Hacken der Verschlüsselungsalgorithmen von Bitcoin führt sofort zu dessen Tod. Nehmen wir an, bei der Implementierung der Algorithmen wird ein Fehler gemacht, und der Angreifer, der ihn findet, erlangt entweder die vollständige oder teilweise Kontrolle über die Blockchain. Außerdem sind die in Bitcoin verwendeten Verschlüsselungsalgorithmen nicht vor Hackerangriffen zukünftiger Quantencomputer geschützt, ihr Aussehen und ihre Implementierung von Quantenalgorithmen – wird der aktuellen Implementierung von Bitcoin ein Ende setzen. Dies kann jedoch durch den Wechsel zu Post-Quanten-Verschlüsselungsalgorithmen gelöst werden.

WebGL + SDL + Emscripten

Am Ende habe ich Mika mit SDL 1 und Emscripten auf WebGL portiert.

Als nächstes beschreibe ich, was im Code geändert werden musste, damit der Build in JavaScript erfolgreich abgeschlossen werden konnte.

  1. Verwenden Sie SDL 1 anstelle von SDL 2. Im Moment gibt es einen SDL 2-Port für Emscripten, aber ich fand es angemessener, den in Emscripten integrierten SDL 1 zu verwenden. Der Kontext wird nicht im Fenster initialisiert, sondern mithilfe von SDL_SetVideoMode und dem SDL_OPENGL-Flag. Der Puffer wird mit dem Befehl SDL_GL_SwapBuffers()
  2. gezeichnet

  3. Aufgrund der Art und Weise, wie JavaScript Schleifen durchführt – Das Rendering wird in einer separaten Funktion platziert und der regelmäßige Aufruf erfolgt über die Funktion emscripten_set_main_loop
  4. Die Montage muss ebenfalls mit dem Schlüssel “-s FULL_ES2=1
  5. erfolgen

  6. Ich musste die Assimp-Bibliothek aufgeben, das Modell aus dem Dateisystem laden und die Textur von der Festplatte laden. Alle notwendigen Puffer wurden auf die Desktop-Version geladen und zur Assemblierung mit emscripten.
  7. in die C-Header-Datei eingefügt

Code:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/9-sdl-gles-obj-textured-assimp-miku-webgl/mikuWebGL

Artikel:
http://blog.scottlogic.com/2014/03/12/native-code-emscripten-webgl-simmer-gently.html
https://kripken.github.io/emscripten-site/docs/porting/multimedia_and_graphics/OpenGL-support.html

Modell:
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

Es gibt nur Miku

Das Ergebnis der Arbeit an der FSGL-Bibliothek mit OpenGL ES und Code:

Als nächstes werde ich beschreiben, wie alles programmiert wurde und verschiedene interessante Probleme gelöst wurden.

Zuerst initialisieren wir den OpenGL ES-Kontext, wie ich im vorherigen Beitrag geschrieben habe. Im Folgenden betrachten wir nur das Rendern und eine kurze Beschreibung des Codes.

Die Matrix beobachtet dich

Diese Figur von Miku im Video besteht aus Dreiecken. Um in OpenGL ein Dreieck zu zeichnen, müssen Sie drei Punkte mit den Koordinaten x, y, z angeben. in 2D-Koordinaten des OpenGL-Kontexts.
Da wir eine Figur zeichnen müssen, die 3D-Koordinaten enthält, müssen wir eine Projektionsmatrix verwenden. Wir müssen das Modell auch drehen, vergrößern oder was auch immer wir tun möchten – Hierzu wird die Modellmatrix verwendet. In OpenGL gibt es kein Konzept einer Kamera; tatsächlich drehen sich Objekte um eine statische Kamera. Hierzu wird eine Ansichtsmatrix verwendet.

Um die Implementierung von OpenGL ES zu vereinfachen – es enthält keine Matrixdaten. Sie können Bibliotheken verwenden, die fehlende Funktionalität hinzufügen, zum Beispiel GLM.

Shader

Um es dem Entwickler zu ermöglichen, alles und in irgendeiner Weise zu zeichnen, muss OpenGL ES Vertex- und Fragment-Shader implementieren. Der Vertex-Shader muss Rendering-Koordinaten als Eingabe erhalten, Transformationen mithilfe von Matrizen durchführen und die Koordinaten an gl_Position übergeben. Fragment- oder Pixel-Shader – Zeichnet bereits Farbe/Textur, wendet Überlagerung an usw.

Ich habe Shader in GLSL geschrieben. In meiner aktuellen Implementierung sind Shader als C-Strings

direkt in den Hauptanwendungscode integriert

Puffer

Der Vertex-Puffer enthält die Koordinaten der Scheitelpunkte (Vertices); dieser Puffer enthält auch Koordinaten für die Texturierung und andere für Shader notwendige Daten. Nachdem Sie den Vertex-Puffer generiert haben, müssen Sie den Zeiger an die Daten für den Vertex-Shader binden. Dies erfolgt mit dem Befehl glVertexAttribPointer, bei dem Sie die Anzahl der Elemente, einen Zeiger auf den Anfang der Daten und die Schrittgröße angeben müssen, die zum Durchlaufen des Puffers verwendet wird. In meiner Implementierung erfolgt die Bindung von Scheitelpunktkoordinaten und Texturkoordinaten für den Pixel-Shader. Es ist jedoch anzumerken, dass die Übertragung der Daten (Texturkoordinaten) an den Fragment-Shader über den Vertex-Shader erfolgt. Um dies zu erreichen, werden die Koordinaten mit Varying angegeben.

Damit OpenGL weiß, in welcher Reihenfolge Punkte für Dreiecke gezeichnet werden müssen – Sie benötigen einen Indexpuffer (Index). Der Indexpuffer enthält die Scheitelpunktnummer im Array. Unter Verwendung dreier solcher Indizes wird ein Dreieck erhalten.

Texturen

Zuerst müssen Sie eine Textur für OpenGL laden/generieren. Hierzu habe ich SDL_LoadBMP verwendet, die Textur wird aus einer BMP-Datei geladen. Es ist jedoch zu beachten, dass nur 24-Bit-BMPs geeignet sind und die Farben darin nicht in der üblichen RGB-Reihenfolge, sondern in BGR gespeichert werden. Das heißt, nach dem Laden müssen Sie den roten Kanal durch einen blauen ersetzen.
Texturkoordinaten werden im Format angegeben UV< /a>, das heißt, Sie müssen nur zwei Koordinaten übertragen. Die Texturausgabe erfolgt im Fragment-Shader. Dazu müssen Sie die Textur in einen Fragment-Shader binden.

Nichts Besonderes

Da OpenGL gemäß unseren Anweisungen 3D bis 2D zeichnet – dann um die Tiefe zu implementieren und unsichtbare Dreiecke auszuwählen – Sie müssen Culling und einen Tiefenpuffer (Z-Puffer) verwenden. In meiner Implementierung ist es mir gelungen, die manuelle Generierung des Tiefenpuffers mithilfe von zwei Befehlen zu vermeiden: glEnable(GL_DEPTH_TEST); und Auswahlen glEnable(GL_CULL_FACE);
Stellen Sie außerdem sicher, dass die Nahebene für die Projektionsmatrix größer als Null ist, denn Die Überprüfung der Tiefe mit einer Null-Nahebene funktioniert nicht.

Rendering

Um den Vertex-Puffer und den Index-Puffer mit etwas Bewusstem zu füllen, zum Beispiel dem Miku-Modell, müssen Sie dieses Modell laden. Hierfür habe ich die Bibliothek assimp verwendet. Miku wurde in eine Wavefront-OBJ-Formatdatei eingefügt, mit assimp geladen und die Datenkonvertierung von assimp in Vertex- und Indexpuffer wurde implementiert.

Das Rendern erfolgt in mehreren Schritten:

  1. Rotieren Sie Miku mithilfe der Modellmatrixrotation
  2. Bildschirm und Tiefenpuffer löschen
  3. Zeichnen von Dreiecken mit dem glDrawElements-Befehl.

Nächste Stufe – Implementierung des Renderings in WebGL mit Emscripten.

Quellcode:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/8-sdl-gles-obj-textured-assimp-miku
Modell:
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

 

Projizieren Sie es

Nachdem ich eine rote Teekanne in 3D gezeichnet habe, betrachte ich es als meine Pflicht, kurz zu beschreiben, wie es gemacht wird.

Modernes OpenGL zeichnet nicht 3D, sondern nur Dreiecke, Punkte usw. in 2D-Bildschirmkoordinaten.
Um zumindest etwas mit OpenGL auszugeben, müssen Sie einen Vertex-Puffer bereitstellen, einen Vertex-Shader schreiben, alle erforderlichen Matrizen (Projektion, Modell, Ansicht) zum Vertex-Shader hinzufügen und alle Eingabedaten damit verknüpfen Rufen Sie für den Shader die Methode Rendering in OpenGL auf. Sieht es einfach aus?


Ok, was ist ein Vertex-Puffer? Liste der zu zeichnenden Koordinaten (x, y, z)
Der Vertex-Shader teilt der GPU mit, welche Koordinaten gezeichnet werden sollen.
Der Pixel-Shader sagt, was gezeichnet werden soll (Farbe, Textur, Mischung usw.)
Die Matrizen übersetzen 3D-Koordinaten in 2D-OpenGL-Koordinaten, die gerendert werden können

In den folgenden Artikeln werde ich Codebeispiele und Ergebnisse bereitstellen.

SDL2 – OpenGL ES

I love Panda3D game engine. But right now this engine is very hard to compile and debug on Microsoft Windows operation system. So as I said some time ago, I begin to develop my own graphics library. Right now it’s based on OpenGL ES and SDL2.
In this article I am going to tell how to initialize OpenGL ES context and how SDL2 helps in this task. We are going to show nothing.

King Nothing

First of all you need to install OpenGL ES3 – GLES 3 libraries. This operation is platform dependant, for Ubuntu Linux you can just type sudo apt-get install libgles2-mesa-dev. To work with OpenGL you need to initialize OpenGL context. There is many ways to do that, by using one of libraries – SDL2, GLFW, GLFM etc. Actually there is no one right way to initialize OpenGL context, but I chose SDL2 because it’s cross-platform solution, code will look same for Windows/*nix/HTML5/iOS/Android/etc.

To install sdl2 on Ubuntu use this command sudo apt-get install libsdl2-dev

So here is OpenGL context initialization code with SDL2:

    SDL_Window *window = SDL_CreateWindow(
            "SDL2 - OGLES",
            SDL_WINDOWPOS_UNDEFINED,
            SDL_WINDOWPOS_UNDEFINED,
            640,
            480,
            SDL_WINDOW_OPENGL
            );
	    

    SDL_GLContext glContext = SDL_GL_CreateContext(window);

After that, you can use any OpenGL calls in that context.

Here is example code for this article:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/3sdl-gles
https://github.com/demensdeum/OpenGLES3-Experiments/blob/master/3sdl-gles/sdlgles.cpp

You can build and test it with command cmake . && make && ./SDLGles

Quanten-Hacking RSA

Neulich habe ich meine Implementierung des RSA-Public-Key-Verschlüsselungsalgorithmus geschrieben. Ich habe auch einen einfachen Hack dieses Algorithmus gemacht, deshalb wollte ich eine kurze Notiz zu diesem Thema schreiben. Die Manipulationssicherheit von RSA basiert auf einem Faktorisierungsproblem. Faktorisierung… Was für ein schreckliches Wort.

Es ist gar nicht so gruselig

Tatsächlich nehmen wir in der ersten Phase der Schlüsselerstellung zwei Zufallszahlen, aber die Zahlen dürfen nur durch sich selbst teilbar sein und eins – Primzahlen.
Nennen wir sie p und q. Als nächstes sollten wir die Zahl n = p *q erhalten. Es wird für die weitere Schlüsselgenerierung verwendet, die Schlüssel wiederum werden zum Ver- und Entschlüsseln von Nachrichten verwendet. In der finalen Version des privaten und öffentlichen Schlüssels wird die Nummer n unverändert übernommen.
Nehmen wir an, wir haben einen der RSA-Schlüssel und eine verschlüsselte Nachricht in unseren Händen. Wir nehmen die Nummer n aus dem Schlüssel und starten Hack.

N faktorisieren

Faktorisierung – Zerlegung einer Zahl in Primfaktoren. Zuerst extrahieren wir die Zahl naus dem Schlüssel (bei echten Schlüsseln können Sie das mit OpenSSL machen), sagen wir n = 35. Dann zerlegen wir sie in einfache Faktoren n = 35 = 5 * 7, das ist und da sind unsere p und q. Jetzt können Sie die Schlüssel mithilfe der empfangenen p, q neu generieren, die Nachricht entschlüsseln und verschlüsseln und dabei die Sichtbarkeit des ursprünglichen Autors sicherstellen.

Qubits sind nicht so einfach

Ist es wirklich möglich, RSA so einfach zu knacken? Tatsächlich nein, die Zahlen p, q sind bewusst groß gewählt, sodass die Faktorisierungsaufgabe auf klassischen Computern sehr lange dauert (teilweise 10 Jahre)< br / >Mit dem Quantenalgorithmus von Shor ist es jedoch möglich, eine Zahl in sehr kurzer Zeit zu faktorisieren. Derzeit wird in Artikeln zu diesem Thema die Zeit für die Multiplikation einer bestimmten Zahl angegeben, also praktisch sofort. Damit Shors Algorithmus funktioniert, ist es notwendig, Quantencomputer mit einer großen Anzahl von Qubits zu implementieren. Im Jahr 2001 faktorisierte IBM die Zahl 15 mithilfe von 7 Qubits. Wir werden also noch lange auf diesen Moment warten müssen, bis wir auf Post-Quanten-Verschlüsselungsalgorithmen umgestiegen sein werden.

Berühre Shor

Peter Shore spricht über seinen Faktorisierungsalgorithmus

Um Shors Algorithmus auf einem Quantensimulator auszuprobieren, können Sie ProjectQ, seine Beispiele umfassen eine Implementierung von shor.py, die es Ihnen ermöglicht, eine vom Benutzer eingegebene Zahl zu faktorisieren. Auf dem Simulator ist die Ausführungszeit deprimierend, aber es scheint eine unterhaltsame und spielerische Simulation des Betriebs eines Quantencomputers zu sein.

Artikel:
http://www.pagedon.com/rsa-explained-simply/my_programming/
http://southernpacificreview.com/2014/01/06/rsa-key-generation-example/
https://0day.work/how-i-recovered-your-private-key-or-why-small-keys-are-bad/

Python-Implementierung von RSA:
https://github.com/demensdeum/RSA-Python

Russian Quantum Hack and Number Generator

[Translation may be, some day]

Эта заметка увеличит длину вашего резюме на 5 см!

Без лишних слов о крутости квантовых компьютеров и всего такого, сегодня я покажу как сделать генератор чисел на реальном квантовом процессоре IBM.
Для этого мы будем использовать всего один кубит, фреймворк для разработки квантового ПО для python – ProjectQ, и 16 кубитовый процессор от IBM, онлайн доступ к которому открыт любому желающему по программе IBM Quantum Experience.

Установка ProjectQ

Для начала у вас должен быть Linux, Python и pip. Какие либо инструкции по установке этих базовых вещей приводить бесполезно, т.к. в любом случае инструкции устареют через неделю, поэтому просто найдите гайд по установке на официальном сайте. Далее устанавливаем ProjectQ, гайд по установке приведен в документации. На данный момент все свелось к установке пакета ProjectQ через pip, одной командой: python -m pip install –user projectq

Ставим кубит в суперпозицию

Создаем файл quantumNumberGenerator.py и берем пример генератора бинарного числа из документации ProjectQ, просто добавляем в него цикл на 32 шага, собираем бинарную строку и переводим в 32-битное число:

import projectq.setups.ibm
from projectq.ops import H, Measure
from projectq import MainEngine
from projectq.backends import IBMBackend

binaryString = ""

eng = MainEngine()

for i in range(1, 33):

 qubit = eng.allocate_qubit()

 H | qubit

 Measure | qubit

 eng.flush()

 binaryString = binaryString + str(int(qubit))

 print("Step " + str(i))

number = int(binaryString, 2)

print("\n--- Quantum 32-Bit Number Generator by demensdeum@gmail.com (2017) ---\n")
print("Binary: " + binaryString)
print("Number: " + str(number))
print("\n---")

Запускаем и получаем число из квантового симулятора с помощью команды python quantumNumberGenerator.py

Незнаю как вы, но я получил вывод и число 3974719468:

--- Quantum 32-Bit Number Generator by demensdeum@gmail.com (2017) ---

Binary: 11101100111010010110011111101100
Number: 3974719468

---

Хорошо, теперь мы запустим наш генератор на реальном квантовом процессоре IBM.

Хакаем IBM

Проходим регистрацию на сайте IBM Quantum Experience, подтверждаем email, в итоге должен остаться email и пароль для доступа.
Далее включаем айбиэмовский движок, меняем строку eng = MainEngine() -> eng = MainEngine(IBMBackend())
В теории после этого вы запускаете код снова и теперь он работает на реальном квантовом процессоре, используя один кубит. Однако после запуска вам придется 32 раза набрать свой email и пароль при каждой аллокации реального кубита. Обойти это можно прописав свой email и пароль прямо в библиотеки ProjectQ.

Заходим в папку где лежит фреймворк ProjectQ, ищем файл с помощью grep по строке IBM QE user (e-mail).
В итоге я исправил строки в файле projectq/backends/_ibm/_ibm_http_client.py:

email = input_fun('IBM QE user (e-mail) > ') -> email = "quantumPsycho@aport.ru"

password = getpass.getpass(prompt='IBM QE password > ') -> password = "ilovequbitsandicannotlie"

Напишите свой email и password со-но.

После этого IBM будет отправлять результаты работы с кубитом онлайн прямо в ваш скрипт, процесс генерации занимает около 20 секунд.

Возможно в дальнейшем я доберусь до работы квантового регистра, и возможно будет туториал, но это не обязательно.
Да прибудет с вами запутанность.

Статья на похожую тему:
Introducing the world’s first game for a quantum computer

Schlechte Roboter auf WebGL basierend auf ThreeJS

Heute wird eine Version des Spiels Bad Robots auf einem experimentellen WebGL-Renderer veröffentlicht, der auf der Bibliothek ThreeJS.
Dies ist das erste OpenGL (WebGL)-Spiel auf der Flame Steel Engine.
Sie können es hier spielen:
http://demensdeum.com/games/BadRobotsGL/

Der auf ThreeJS basierende IOSystem-Quellcode ist hier verfügbar:
https://github.com/demensdeum/FlameSteelEngineGameToolkitWeb

Porting SDL C++ Game to HTML5 (Emscripten)

[Translation may be some day]

За последний год я написал простейший движок Flame Steel Engine и набор классов для игровой разработки Flame Steel Engine Game Toolkit. В данной статье я опишу как производил портирование движка и SDL игры Bad Robots на HTML 5, с использованием компилятора Emscripten.

Установка Hello World – Emscripten

Для начала нужно установить Emscripten. Простейшим вариантом оказалось использование скрипта emsdk для Linux. На официальном сайте данный тип установки называется как “Portable Emscripten SDK for Linux and OS X“. Внутри архива есть инструкция по установке с использованием скрипта. Я производил установку в директорию ~/emsdk/emsdk_portable.

После установки emscripten нужно проверить корректность работы компилятора, для этого создаем простейший hello_world.cpp и собираем его в hello_world.html с помощью команд:

source ~/emsdk/emsdk_portable/emsdk_env.sh
emcc hello_world.cpp -o hello_world.html

После компиляции в папке появится hello_world.html и вспомогательные файлы, откройте его в лучшем браузере Firefox, проверьте что все работает корректно.

Портирование кода игры

В javascript нежелательно вызывать бесконечный цикл – это приводит к зависанию браузера. На данный момент корректная стратегия – запрашивать один шаг цикла у браузера с помощью вызова window.requestAnimationFrame(callback)

В Emscripten данное обстоятельство решено с помощью вызова:

emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop);

Таким образом, нужно изменить код игры для корректного вызова метода emscripten. Для этого я сделал глобальный метод GLOBAL_fsegt_emscripten_gameLoop, в котором вызываю шаг цикла игрового контроллера. Главный игровой контроллер также вынесен в глобальную видимость:

#ifdef __EMSCRIPTEN__

void GLOBAL_fsegt_emscripten_gameLoop() {

GLOBAL_fsegt_emscripten_gameController->gameLoop();

}
#endif

Также для обработки специфических для Emscripten моментов, нужно использовать макрос __EMSCRIPTEN__.

Ресурсы и оптимизация

Emscripten поддерживает ресурсы и сборку с оптимизацией.

Для добавления изображений, музыки и прочего, положите все файлы в одну папку, например data. Далее в скрипт сборки добавьте:

emcc <файлы для сборки> –use-preload-plugins –preload-file data

Флаг –use-preload-plugins включает красивый прелоадер в углу экрана, –preload-file добавляет указанный ресурс в файл <имя проекта>.data
Код постоянно останавливался с ошибками доступа к ресурсам, пока я не включил оба этих флага. Также стоит заметить что для корректного доступа к ресурсам, желательно запускать игру на https (возможно и http) сервере, или отключить защиту локального доступа к файлам в вашем браузере.

Для включения оптимизации добавьте флаги:

-s TOTAL_MEMORY=67108864 -O3 -ffast-math

TOTAL_MEMORY – оперативная память в байтах(?) необходимая для корректной работы игры. Вы можете использовать флаг для динамического выделения памяти, но тогда часть оптимизаций работать не будет.

Производительность

Код javascript из C++ работает гораздо медленнее, даже со включенными оптимизациями. Поэтому если ваша цель это разработка для HTML5, то приготовьтесь к ручной оптимизации алгоритмов игры, паралелльному тестированию, также к написанию javascript кода вручную в особо узких местах. Для написания javascript кода используется макрос EM_ASM. Во время реализации рейкастера на emscripten, мне удалось добиться повышения fps с 2-4 до 30 с помощью прямого использования методов canvas.drawImage, в обход обертки SDL->Canvas, что почти приравнялось к написанию всего на javascript.

Поддержка SDL

На данный момент почти не работает SDL_TTF, поэтому отрисовка шрифта для Game Score в BadRobots очень проста. SDL_Image, SDL_Mixer работают корректно, в mixer я проверил только проигрывание музыки.

Исходный код Flame Steel Engine, Flame Steel Engine Game Toolkit, игры Bad Robots:

https://github.com/demensdeum/BadRobots
https://github.com/demensdeum/FlameSteelEngine
https://github.com/demensdeum/FlameSteelEngineGameToolkit

Статья на эту тему:

https://hacks.mozilla.org/2012/04/porting-me-my-shadow-to-the-web-c-to-javascriptcanvas-via-emscripten/

ECS verdünnen


Commission: Mad Scientist by Culpeo-Fox on DeviantArt

In diesem Artikel werde ich das ECS-Muster und meine Implementierung im Flame Steel Engine Game Toolkit grob beschreiben. Das Entity Component System-Muster wird in Spielen verwendet, einschließlich. auf der Unity-Engine. Jedes Objekt im Spiel ist eine Essenz, die mit Komponenten gefüllt ist.  Warum ist das notwendig, wenn es OOP gibt?
Dann können Sie die Eigenschaften, das Verhalten und die Anzeige von Objekten direkt während des Spiels ändern. Solche Dinge gibt es in realen Anwendungen nicht; die Dynamik sich ändernder Parameter, Objekteigenschaften, Anzeige und Sound ist charakteristischer für Spiele als für Buchhaltungssoftware.


An Bananen sind wir nicht vorbeigegangen

Nehmen wir an, wir haben eine Bananenklasse in unserem Spiel. Und der Spieleentwickler wollte, dass Bananen als Waffen verwendet werden. Nehmen wir an, dass Bananen in der aktuellen Architektur nichts mit Waffen zu tun haben. Eine Banane zur Waffe machen? Alle Gegenstände zu Waffen machen?
ECS bietet eine Lösung für dieses dringende Problem – Alle Objekte im Spiel müssen aus Komponenten bestehen. Früher war Banana die Banana-Klasse, jetzt machen wir sie und alle anderen Objekte zur Entity-Klasse und fügen ihnen Komponenten hinzu. Nehmen wir an, eine Banane besteht nun aus folgenden Komponenten:

  1. Positionskomponente (Koordinaten in der Spielwelt – x, y, z)
  2. Rotationskomponente (x-, y-, z-Koordinaten)
  3. Kalorienanteil einer Banane (die Hauptfigur sollte nicht zu dick werden)
  4. Bananen-Bildkomponente

Jetzt fügen wir eine neue Komponente für alle Bananen hinzu, nämlich eine Flagge, die besagt, dass sie als Waffe verwendet werden kann – Waffenkomponente. Wenn das Spielsystem nun erkennt, dass sich ein Spieler einer Banane genähert hat, prüft es, ob eine Waffenkomponente auf der Banane vorhanden ist, und wenn dies der Fall ist, bewaffnet es den Spieler mit einer Banane.
In meinem Spiel Flame Steel Call Of The Death Mask wird durchgehend das ECS-Muster verwendet. Objekte bestehen aus Komponenten, und Komponenten selbst können Komponenten enthalten. Im Allgemeinen ist die Aufteilung des Objekts < – > Die Komponente fehlt in meiner Implementierung, aber das ist sogar ein Plus.

screenshot_2016-09-24_14-33-43

Die Schrotflinte in diesem Screenshot ist eine Komponente des Spielers, und gleichzeitig hängt die zweite Schrotflinte einfach als gewöhnliches Objekt auf der Spielkarte.
In diesem Screenshot laufen zwei Systeme: – Szenen-Renderer und Schnittstellen-Renderer. Der Szenen-Renderer arbeitet mit der Schrotflinten-Bildkomponente auf der Karte, der Schnittstellen-Renderer arbeitet mit der Schrotflinten-Bildkomponente in den Händen des Spielers.

Verwandte Links:
https://habrahabr.ru/post/197920/
https://www.youtube.com/watch?v=NTWSeQtHZ9M

Architektur des Flame Steel Engine-Spiel-Toolkits

Heute werde ich über die Architektur des Toolkits für die Spieleentwicklung Flame Steel Engine Game Toolkit sprechen.
Mit dem Flame Steel Engine Game Toolkit können Sie Spiele erstellen, die auf der Flame Steel Engine basieren:
flamesteelgametoolkitschematics

Alle Klassen der Flame Steel Engine beginnen mit dem Präfix FSE (Flame Steel E ngine) undFSEGT (FlameSteelEngineG stark>ame Toolkit) für Toolkit.
Spielszenen, Objekte und Schaltflächen sind allesamt Unterklassen von FSEObject und sollten sich innerhalb der Klasse FSEGTGameData befinden. Jedes FSEObject muss die FSESerialize-Schnittstelle implementieren. Dadurch können Sie Spieldaten speichern/laden und einen Speichermechanismus bereitstellen.
Die Klasse FSEController arbeitet mit Objekten der Klasse FSEObject. Das Toolkit verfügt über eine Basis-Spielszenen-Controller-Klasse – FSEGTGameSceneController, Sie können diese Klasse erben, um Ihre Spiellogik zu implementieren.
IOSystem ist ein Objekt der FSEGTIOSystem-Schnittstelle. Diese Schnittstelle enthält FSEGTRenderer, FSEGTInputController, FSEGTUIRenderer.
FSEGTIOSystem muss einen Renderer implementieren, Daten von der Tastatur und Joysticks (Eingabegeräten) empfangen und die Darstellung von Schnittstellenelementen für zugängliche Eingabe-/Ausgabesysteme dieser Plattform.
Derzeit wurde ein Renderer implementiert, ein Tastatur-Controller basierend auf der SDL-Bibliothek, er ist im FSEGTIOSDLSystem-Klasse

Flame Steel Engine Raycaster Demo
Flame Steel Engine Raycaster Demo

Zukünftige Pläne zur Erstellung eines IOSystems basierend auf OpenGL, die Klasse wird FSEGTIOGLSystem heißen . Wenn Sie ein IOSystem basierend auf einer beliebigen Plattform erstellen möchten, müssen Sie die FSEGTIOSystem-Schnittstelle verwenden und dafür den FSEGTRenderer-Renderer FSEGTInputController implementieren Plattform .

Quellcode der Flame Steel Engine, Toolkit, Spiel:
https://github.com/demensdeum/FlameSteelCallOfTheDeathMask