DRYが重要な理由

DRY のトピックに関する記事は数多くありますが、オリジナルのソースである Andy Hunt と Dave Thomas による「The Pragmatist Programmer」を読むことをお勧めします。しかし、ソフトウェア開発におけるこの原則について疑問を抱いている開発者がいかに多いかがわかります。

DRY 原則では、同じことを繰り返してはいけないと定められており、これはコードとプログラマーとして実行するプロセスの両方に当てはまります。 DRY に違反するコード例:

class Client {
    public let name: String
    private var messages: [String] = []
    
    init(name: String) {
        self.name = name
    }
    
    func receive(_ message: String) {
        messages.append(message)
    }
}

class ClientController {
    func greet(client: Client?) {
        guard let client else {
            debugPrint("No client!")
            return
        }
        client.receive("Hello \(client.name)!")
    }

    func goodbye(client: Client?) {
        guard let client else {
            debugPrint("No client!!")
            return
        }
        client.receive("Bye \(client.name)!")
    }
}

挨拶メソッドと別れのメソッドでわかるように、Client クラスのオプションのインスタンスが渡されます。これは nil かどうかをチェックする必要があり、それから作業を開始できます。 DRY メソッドに準拠するには、クラス インスタンスの重複した nil チェックを削除する必要があります。これはさまざまな方法で実装できます。1 つのオプションは、インスタンスをクラス コンストラクターに渡すことで、その後はチェックが必要なくなります。

ClientController を単一の Client インスタンスに特化することで DRY を維持します。

class Client {
    public let name: String
    private var messages: [String] = []
    
    init(name: String) {
        self.name = name
    }
    
    func receive(_ message: String) {
        messages.append(message)
    }
}

class ClientController {
    private let client: Client

    init(client: Client) {
        self.client = client
    }

    func greet() {
        client.receive("Hello \(client.name)!")
    }

    func goodbye() {
        client.receive("Bye \(client.name)!")
    }
}

DRY は、ソフトウェア開発中に発生するプロセスにも関係します。開発チームが独自にリリースを市場にアップロードし、ソフトウェア開発から気をそらさなければならない状況を想像してみましょう。これも DRY 違反です。この状況は、開発者が満たす特定の条件に従って、リリースが自動的にリリースされる CI/CD パイプラインに接続することで解決されます。

一般に、DRY はプロセスとコードの両方に繰り返しがないことを意味します。これは人的要因の存在によっても重要です。繰り返しが少なくノイズの多いコードが含まれるコードは、エラーのチェックが容易です。自動化されたプロセスでは、人間が関与しないため、プロセスの実行中に間違いを犯すことがなくなります。

スティーブ・ジョブズの格言は、「書く必要のなかったコード行は、デバッグする必要のないコード行です。」です。

ソース

https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/
https://youtu.be/-msIEOGvTYM

Swift または Objective-C 用の iOS の開発をお手伝いします

私は現在、Fiverr で iOS 開発者としてのサービスを提供していることをお知らせします。高品質の iOS アプリの開発や既存のプロジェクトの改善にサポートが必要な場合は、私のプロフィールをご覧ください。
https://www.fiverr.com/s/Q7x4kb6

あなたのプロジェクトに携わる機会があれば嬉しいです。
電子メール: demensdeum@gmail.com
電報: https://t.me/demensdeum

macOS 上の Qt アプリケーションの動的リンク

本日、macOS および M1/M2/M3/M4 プロセッサ (Apple Silicon) を搭載した Apple デバイス用の RaidenVideoRipper のバージョンをリリースしました。 RaidenVideoRipper は、ビデオ ファイルの一部を新しいファイルに切り取ることができる迅速なビデオ編集アプリケーションです。 gif を作成したり、オーディオ トラックを mp3 にエクスポートしたりすることもできます。

次に、これを実現するために使用したコマンドについて簡単に説明します。ここで何が起こっているかの理論、ユーティリティのドキュメントは、次のリンクで読むことができます。
https://www.unix.com/man-page/osx/1/otool/
https://www.unix.com/man-page/osx/1/install_name_tool/
https://llvm.org/docs/CommandGuide/llvm-nm.html
https://linux.die.net/man/1/file
https://www.unix.com/man-page/osx/8/SPCTL/
https://linux.die.net/man/1/chmod
https://linux.die.net/man/1/ls
https://man7.org/linux/man-pages/man7/xattr.7.html
https://doc.qt.io/qt-6/macos-deployment.html

開始するには、macOS に Qt をインストールし、Qt デスクトップ開発用の環境もインストールします。この後、たとえば Qt Creator でプロジェクトをアセンブルし、アプリケーションをエンド ユーザーに配布するときに外部動的ライブラリとの依存関係が正しく処理されるようにするために必要なことについて説明します。

アプリケーションの YOUR_APP.app/Contents フォルダーに Frameworks ディレクトリを作成し、その中に外部依存関係を配置します。たとえば、RaidenVideoRipper アプリケーションのフレームワークは次のようになります。

Frameworks
├── DullahanFFmpeg.framework
│   ├── dullahan_ffmpeg.a
│   ├── libavcodec.60.dylib
│   ├── libavdevice.60.dylib
│   ├── libavfilter.9.dylib
│   ├── libavformat.60.dylib
│   ├── libavutil.58.dylib
│   ├── libpostproc.57.dylib
│   ├── libswresample.4.dylib
│   └── libswscale.7.dylib
├── QtCore.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtCore -> Versions/Current/QtCore
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtGui.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtGui -> Versions/Current/QtGui
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtMultimedia.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtMultimedia -> Versions/Current/QtMultimedia
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtMultimediaWidgets.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtMultimediaWidgets -> Versions/Current/QtMultimediaWidgets
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtNetwork.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtNetwork -> Versions/Current/QtNetwork
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
└── QtWidgets.framework
    ├── Headers -> Versions/Current/Headers
    ├── QtWidgets -> Versions/Current/QtWidgets
    ├── Resources -> Versions/Current/Resources
    └── Versions

簡単にするために、ネストの 2 番目のレベルのみを出力しました。

次に、アプリケーションの現在の動的依存関係を出力します。

otool -L RaidenVideoRipper 

RaidenVideoRipper.app/Contents/MacOS にある RaidenVideoRipper バイナリの出力:

RaidenVideoRipper:
	@rpath/DullahanFFmpeg.framework/dullahan_ffmpeg.a (compatibility version 0.0.0, current version 0.0.0)
	@rpath/QtMultimediaWidgets.framework/Versions/A/QtMultimediaWidgets (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtWidgets.framework/Versions/A/QtWidgets (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtMultimedia.framework/Versions/A/QtMultimedia (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtGui.framework/Versions/A/QtGui (compatibility version 6.0.0, current version 6.8.1)
	/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 2575.20.19)
	/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/Metal.framework/Versions/A/Metal (compatibility version 1.0.0, current version 367.4.0)
	@rpath/QtNetwork.framework/Versions/A/QtNetwork (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtCore.framework/Versions/A/QtCore (compatibility version 6.0.0, current version 6.8.1)
	/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
	/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers (compatibility version 1.0.0, current version 709.0.0)
	/System/Library/Frameworks/AGL.framework/Versions/A/AGL (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1800.101.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)

Qt の RaidenVideoRipper と dulahan_ffmpeg の依存関係に見られるように。 Dullahan FFmpeg は FFmpeg のフォークであり、その機能を動的ライブラリにカプセル化し、C ルーチンを使用して現在の実行の進行状況とキャンセルを取得する機能を備えています。
次に、install_name_tool を使用して、アプリケーションと必要なすべてのライブラリのパスを置き換えます。

このためのコマンドは次のとおりです。

install_name_tool -change old_path new_path target

使用例:

install_name_tool -change /usr/local/lib/libavfilter.9.dylib @rpath/DullahanFFmpeg.framework/libavfilter.9.dylib dullahan_ffmpeg.a

正しいパスをすべて入力すると、アプリケーションが正しく起動するはずです。ライブラリへのすべてのパスが相対パスであることを確認し、バイナリを転送して、再度開きます。
エラーが表示された場合は、otool を使用してパスを確認し、install_name_tool を使用して再度変更します。

依存関係の混同によるエラーもあります。置き換えたライブラリにテーブル内にシンボルがない場合は、次のようにシンボルの有無を確認できます。

nm -gU path

実行すると、ライブラリまたはアプリケーションのシンボル テーブル全体が表示されます。
間違ったアーキテクチャの依存関係をコピーした可能性もあります。次のファイルを使用してこれを確認できます。

file path

ファイル ユーティリティは、ライブラリまたはアプリケーションがどのアーキテクチャに属しているかを示します。

Qt では、YOUR_APP.app ディレクトリの Content フォルダーに Plugins フォルダーも必要です。プラグインを Qt から Contents にコピーします。次に、アプリケーションの機能を確認します。その後、Plugins フォルダーの最適化、このフォルダーからの要素の削除、およびアプリケーションのテストを開始できます。

macOS セキュリティ

すべての依存関係をコピーし、動的リンクのパスを修正した後、開発者の署名でアプリケーションに署名し、さらに公証のためにアプリケーションのバージョンを Apple に送信する必要があります。

開発者ライセンスの 100 ドルを持っていない場合、または何も署名したくない場合は、アプリケーションの起動方法についてユーザーに指示を書きます。

この手順は RaidenVideoRipper でも機能します。

  • ゲートキーパーを無効にする: spctl –master-disable
  • プライバシーとセキュリティで任意のソースからの起動を許可: アプリケーションがどこからでも切り替えられるようにする
  • zip または dmg アプリケーションからダウンロードした後、隔離フラグを削除します: xattr -d com.apple.quarantine app.dmg
  • 検疫フラグ (com.apple.quarantine) が欠落していることを確認します: ls -l@ app.dmg
  • 必要に応じてアプリケーションを起動するための確認を「プライバシーとセキュリティ」に追加します

隔離フラグによるエラーは通常、ユーザーの画面に「アプリケーションが破損しています」というエラーが表示されて再現されます。この場合、メタデータから隔離フラグを削除する必要があります。

Apple Silicon 用の RaidenVideoRipper をビルドするためのリンク:
https://github.com/demensdeum/RaidenVideoRipper/releases/download/1.0.1.0/RaidenVideoRipper-1.0.1.0.dmg

ffmpegを使用したビデオ安定化

ビデオを安定させて手ぶれを除去したい場合は、「ffmpeg」ツールが強力なソリューションを提供します。組み込みフィルター `vidstabdetect` と `vidstabtransform` のおかげで、複雑なビデオ エディターを使用せずにプロフェッショナルな結果を達成できます。

仕事の準備

始める前に、`ffmpeg` が `vidstab` ライブラリをサポートしていることを確認してください。 Linux では、次のコマンドでこれを確認できます。

bash  
ffmpeg -filters | grep vidstab  

ライブラリがインストールされていない場合は、追加できます。

sudo apt install ffmpeg libvidstab-dev  

brew による macOS のインストール:

brew install libvidstab
brew install ffmpeg

それでは、プロセスに進みましょう。

ステップ 1: 動作分析

まず、ビデオの動きを分析し、安定化パラメータを含むファイルを作成する必要があります。

ffmpeg -i input.mp4 -vf vidstabdetect=shakiness=10:accuracy=15 transfile=transforms.trf -f null -  

パラメータ:

揺れ: ビデオの揺れレベル (デフォルトは 5、より複雑な場合は 10 まで増やすことができます)。
精度: 分析精度 (デフォルトは 15)。
transfile: モーションパラメータを保存するファイル名。

ステップ 2: 安定化を適用する

これで、変換ファイルを使用して安定化を適用できるようになりました。

ffmpeg -i input.mp4 -vf vidstabtransform=input=transforms.trf:zoom=5 output.mp4

パラメータ:

input: 変換パラメータを含むファイル (最初のステップで作成) を指します。
ズーム: 黒いエッジを除去するためのズーム係数 (例: 5 – アーティファクトが除去されるまで自動ズーム)。

Bistr による自動コード分析

プロジェクトのソース コードを分析する必要があるが、プロセスを自動化し、コンピューターのローカル機能を使用したい場合は、Bistr ユーティリティが優れたソリューションとなります。この記事では、このユーティリティが Ollama 機械学習モデルを使用したコード分析にどのように役立つかを見ていきます。

Bistr とは何ですか?

Bistr は、コード分析と処理のために Ollama などのローカル LLM (大規模言語モデル) モデルを統合できるソース コード分析ユーティリティです。 Bistr を使用すると、Python、C、Java、JavaScript、HTML などのさまざまなプログラミング言語でファイルを解析できます。

Bistr は、このモデルを使用して、特定のクエリに対してファイルをチェックします。たとえば、コードまたはその一部の機能に関する質問に対する答えを見つけます。これにより、プロジェクトの開発、テスト、保守に役立つ構造化分析が提供されます。

Bistr はどのように機能しますか?

  • 状態の読み込み: 分析を開始すると、ユーティリティは分析状態が以前に保存されているかどうかを確認します。これにより、同じファイルを再度解析することなく、中断したところから再開できるようになります。
  • コード分析: 各ファイルは Ollama モデルを使用して分析されます。ユーティリティは、コードの特定の部分を分析するためにモデルにリクエストを送信します。このモデルは、クエリに応じてコードの関連性に関する情報を返します。また、特定のフラグメントがタスクに関連する理由をテキストで説明します。
  • 状態の保存: 各ファイルの分析後に状態が更新されるため、次回は最新の情報を使用して続行できます。
  • 結果の出力: すべての分析結果は HTML ファイルにエクスポートできます。このファイルには、関連性によるファイルのランキングを含む表が含まれており、コードのどの部分がさらなる分析にとって最も重要であるかを理解するのに役立ちます。

インストールと起動

Bistr を使用するには、ローカル マシンに LLM モデルを提供するプラットフォームである Ollama をインストールして実行する必要があります。 macOS、Windows、Linux 用の Ollama をインストールする手順を以下に説明します。

Bistr の最新バージョンを git からダウンロードします。
https://github.com/demensdeum/Bistr/

Ollama と Bistr をインストールした後、コード分析を実行できます。これを行うには、ソース コードを準備し、分析するファイルが含まれるディレクトリへのパスを指定する必要があります。このユーティリティを使用すると、中断したところから分析を続けることができ、さらに分析を容易にするために結果を HTML 形式でエクスポートする機能も提供します。

分析を実行するコマンドの例:


python bistr.py /path/to/code --model llama3.1:latest --output-html result.html --research "What is the purpose of this function?"

このチームでは:

–model は、分析に使用するモデルを指定します。
–output-html は、解析結果を HTML ファイルに保存するパスを指定します。
–research を使用すると、コードを分析して答えてほしい質問をすることができます。

Bistr を使用する利点

  • ローカル実行: クラウド サービスに接続する必要がなく、分析がコンピュータ上で実行されるため、プロセスが高速化されます。
  • 柔軟性: さまざまなプログラミング言語でコードを分析できます。
  • 自動化: すべてのコード レビュー作業が自動化されるため、特に大規模なプロジェクトで作業する場合に時間と労力が節約されます。

ollamを使用したローカルニューラルネットワーク

ChatGPT のようなものを起動したいと思っていて、Nvidia RTX ビデオ カードなどのかなり強力なコンピューターを持っている場合は、ollam プロジェクトを実行できます。これにより、既製の LLM モデルの 1 つを使用できるようになります。ローカルマシンは完全に無料です。 ollama は、ChatGPT の方法で LLM モデルと通信する機能を提供し、最新バージョンでは、画像を読み取り、出力データを json 形式でフォーマットする機能も発表されました。

また、プロジェクト自体も Apple M2 プロセッサを搭載した MacBook で実行しましたが、AMD の最新モデルのビデオ カードがサポートされていることもわかっています。

macOS にインストールするには、ollam Web サイトにアクセスしてください。
https://ollama.com/download/mac

「macOS 用のダウンロード」をクリックすると、ollama-darwin.zip 形式のアーカイブがダウンロードされます。アーカイブ内には Ollama.app があり、これを「アプリケーション」にコピーする必要があります。この後、Ollama.app を起動します。おそらく、初回起動時にインストール プロセスが発生します。その後、トレイにオラマのアイコンが表示されます。トレイは右上の時計の隣にあります。

その後、通常の macOS ターミナルを起動し、コマンドを入力して ollam モデルをダウンロード、インストールし、実行します。利用可能なモデル、説明、およびその特性のリストは、ollam の Web サイトでご覧いただけます。
https://ollama.com/search

発売時にビデオカードに適合しない場合は、パラメータが最も少ないモデルを選択してください。

たとえば、llama3.1:latest モデルを実行するコマンドは次のとおりです。


ollama run llama3.1:latest

Windows と Linux のインストールは一般的に似ていますが、場合によっては ollam インストーラーがあり、さらに Powershell を介してインストーラーを操作します。
Linux の場合、インストールはスクリプトを使用して行われますが、特定のパッケージ マネージャーのバージョンを使用することをお勧めします。 Linux では、通常の bash ターミナル経由で ollam を起動することもできます。

情報源
https://www.youtube.com/watch?v=Wjrdr0NU4Sk
https://ollama.com

Macbook M2 上のアンリアル エンジン

Apple プロセッサを搭載した Macbook で Unreal Engine 5 Editor を実行できた場合、これが非常に遅いことに気付いたかもしれません。

エディターとエンジンのパフォーマンスを向上するには、[エンジンのスケーラビリティ設定] -> [中] を設定します。この後、エンジンはあまり美しくない描画を開始しますが、MacBook 上でエンジンを正常に使用できるようになります。

WordPressのモバイルメニューを修正する


document.addEventListener('DOMContentLoaded', function() {
    new navMenu('primary');
    new navMenu('woo');
});

また、Seedlet テーマを使用しているときに iOS/Android で WordPress ブログのブログ メニューを数年間開いていない場合は、以下を追加するだけです。
ファイル wp-content/主題/seedlet/assets/js/primary-navigation.js を関数内で閉じ、デフォルトのサブスクリプション ウィンドウの横にある addEventListener ‘load’ を追加します。

Radio-Maximum-Electron

Radio Maximum Electron は、Windows、Linux、および macOS オペレーティング システムを実行しているコンピュータで Radio Maximum ラジオ局のストリームを聴くように設計された強力で便利なアプリケーションです。このプレーヤーは使いやすさと高機能を兼ね備えており、最小限の労力でライブ ストリーミングにアクセスできます。

GitHub からアプリケーションをダウンロードするだけです。

https://github.com/demensdeum/Radio-Maximum-Electron/releases

著者はRadio Maximumとは何の関係もなく、ただこのラジオが大好きなだけです。
主な機能は Nativifier プロジェクトによって実装されます

https://github.com/nativefier/nativefier

MIT ビルド スクリプトのライセンス。ランタイムには独自のライセンスがあります。

水中シロクマ冒険

ThreeJS を使用して迷路を無限に生成するシンプルなゲームです。

「ファミリーゲーム」をテーマにした3日間のゲームジャム「スタート・ザ・ゲーム」の一環として制作された。

極地探検家の子が母親と一緒に氷の上を歩いていたところ、突然災害が発生しました。氷が割れ、氷海の海に落ちました。お母さんにはクマを救う時間がなく、クマは神秘的な水中洞窟に迷い込んでしまいました。驚いたことに、彼は水中でも呼吸できることに気づきました。この罠から抜け出す方法は 1 つだけです。深海を乗り越え、謎を解き、攻撃的なサメと戦うことです。攻撃的なサメは、狙いを定めてリンゴを投げることで撃退することができます。

彼の目標は、深海の危険を乗り越え、謎を解きながら、この水中の罠から抜け出して母親の元に戻る方法を見つけることです。

https://demensdeum.com/demos/arctica/

Nixy Player

Nixy Player – 小型で拡張可能なクロスプラットフォームの JavaScript ランタイム。

クロスプラットフォーム: Windows、macOS、Linux だけでなく、C++ およびダイナミック ライブラリをサポートする他のプラットフォームでも利用できます。
軽量: リソース消費を最小限に抑え、効率的なパフォーマンスを実現します。
拡張可能: プラグインや追加ライブラリで簡単に拡張できるように設計されています。

最新のリリースとアップデートについては、リリース ページにアクセスしてください。
https://github.com/demensdeum/NixyPlayer/releases/

Raiden Video Ripper

Raiden Video Ripper は、ビデオ編集とフォーマット変換のために設計されたオープンソース プロジェクトです。 Qt 6 (Qt Creator) を使用して構築されており、ビデオをトリミングして MP4、GIF、WebM 形式に変換できます。動画から音声を抽出してMP3形式に変換することもできます。
Интерфейс RaidenVideoRipper

コスタリカからの静止画 4K 60fps HDR (ULTRA HD)
https://www.youtube.com/watch?v=LXb3EKWsInQ
最新のリリースとアップデートについては、リリース ページにアクセスしてください。
https://github.com/demensdeum/RaidenVideoRipper/releases

Donki Hills

1 か月で、Unreal Engine 5 を使用して面白いギャグ、パロディ ゲームを作成しました。開発は Twitch ストリームで行われました。

このゲームのストーリーは、平凡なロシア人男性のジェームスが Tinder でマリアという女の子を見つけたものの、ロシアでの制裁と Tinder の終了により彼女と連絡が取れなくなってしまったというものです。今、彼に残っているのは彼女の写真のスクリーンショットだけであり、グーグルマップを使って写真が撮られた場所、ノボシビルスク近郊のティキエ・ドンキ村を見つけた。ジェームズはマリアを探しにそこへ行くが…。

https://demensdeum.itch.io/donki-hills

ロボットディフェンダー

ソフトウェア機能の正しい動作について議論していると、ユーザーの視点から見た機能が奇妙で非論理的に見える状況に遭遇することがよくあります。プロダクトオーナーとのディスカッションは次のようなものでした。

–ここには明らかに問題行動があります
が。
–まあ、リリースしてユーザーから不満が出始めたら修正します
– ???まあ、わかりました…

それはうまくいく計画のように思えますよね?予算が少なく、納期が厳しく、リサーチが不十分であったり、UI/UX スペシャリストが不足しているチームにとって、かなり最適なアルゴリズムです。何かあったらユーザーからクレームが来るから大丈夫
です。
Google 検索すると、このメソッドのソースは記事「–」から来ていることがわかります。 「苦情主導型開発」 by コーディングホラー

かつて私は、ドクターズ ソーセージなどの食品を 300 ルーブルで販売していました。スーパーマーケットの端末を使って、私はこのソーセージが代金を支払われたことを確信して店を出ました。端末は小切手を印刷しないことを提案し、私はこの小切手で貴重な紙を無駄にしないために同意しました。各製品の商品を「パンチ」するプロセス中に、端末はすべてが正しく機能したことを示すきしむ音を発しました。さらに、音声アラートとともに、端末はバーコード スキャナのバックライトでウィンクしました。

翌日、私は再びスーパーマーケットに食料品を買いに行き、食料品を端末に入れました。出口で私を出迎えたのは、濃いひげを生やした南部風の男性で、スマートフォンを差し出しながらこう言いました。 「カメラに映ってるの?」と彼の携帯を見ると、Arch Enemy の頭蓋骨などが描かれたメロディック・デス・メタルの T シャツを着た自分の姿が見えました。それを疑う理由はありませんでした。
「はい、私です。どうしたんですか?」と男は目を細めて言いました。「昨日はソーセージを殴らなかったね。」…すごい

彼が誰で、どのようにしてこのような結論に至ったのかについて簡単に調査した後、彼は店の天井に掛かっているビデオを見せてくれました。そのビデオでは私がソーセージをパンチしており、端末がスキャナーのバックライトで点滅しています。私はソーセージを袋に入れました。

–スキャナーの動作を動画で
ご紹介します。
–何も役に立たなかったので、ソーセージの代金を支払いましょう!

この態度に少し驚いた私は、端末が正常に動作している兆候がすべて見られたため、ソフトウェアの改善が必要であることを苦情報告書に書いてもらうように依頼しましたが、実際には画面上にそのことを示すことなく、単にバグがあっただけでした。いずれにせよ。

彼と上司との10分間の口論の後、すぐに従業員とクソみたいな端末を擁護しようと駆けつけた上司は、管理者の女の子に電話して苦情の本を持ってきて医師の医者を殴らせることにした。ソーセージ。

その日、私はユーザーがハードウェア製品やソフトウェア製品について苦情を言うことがいかに難しいか、そしておそらく「人々は苦情を言うだろう」というマントラが当てはまっていることに気づきました。修正しましょう」は非常にうまく機能しません。主な理由は、壊れたロボットや壊れたソフトウェア ソリューションを擁護する人々です。わかりやすくするために、私は新しい用語を導入することを提案します。壊れたロボットの擁護者、そして壊れたシステムの擁護者。

一般のユーザーは、ZasRoshniks に邪魔されるため、端末の誤動作について文句を言うことができません。ZasRoshniks は、何らかの理由で操作するマシンに愛着を持ち、おそらくマシンをある種の生命体とみなして愛し始め、何も存在しないことを忘れます。そこに住んでいます。

同様の状況が ZaSSoshniki でも発生します。これらの人々は、ユーザーや他の開発者からの苦情にもかかわらず、フレームワーク、プログラミング言語、またはその他のソフトウェア製品の愚かな欠点を擁護して口から泡を立てることがあります。
ZaSSoshnik との典型的な会話は次のとおりです。

–ここで何かが機能しません。ドキュメントによれば、すべてが正しいようです
–ああ、2005 年のマニュアルを読んでいないのですね。一番下に PROGRAM_START:6969
を追加する必要があると小さな文字で書かれています。
– ???えー

そのような人々は、自分自身がどのようにして自分自身や他の人々の問題、間違い、時間とお金の無駄の拡大に貢献しているのかを理解していない可能性があります。ソフトウェアやハードウェアのソリューションに関する明らかでない事柄や問題が隠蔽されていれば、デジタル変革は不可能であるため、それらのせいで誰もが苦しんでいます。
私は、英国郵便局の Horizo​​n ソフトウェアのバグが人々を借金に追い込み、結婚生活を破壊し、何十年にもわたって人々の生活を台無しにした最近の話を知っています。このすべては、ソフトウェアの問題について沈黙を守り、ソフトウェアを「保護」していた人々の黙認のおかげで続きました。彼。

友人の皆さん、ZaSRoshniks や ZaSSoshniks にならないでください。自分が使用するツールを割り引いて扱いましょう。そうしないと、未来の新しいデジタル世界の人質のように、クソ壊れたシステムに完全に奴隷にされることになります。できない人のために –少なくとも、動作していない干渉するソフトウェア/ハードウェアに注意を払おうとする他の人を困らせないでください。なぜなら、これらの製品の開発者は–に同意しているからです。 「ユーザーが苦情を言い始めたら、私たちはそれを修正します。」

出典
https://blog.codinghorror.com/complaint-driven-development/< /a>
https://habr.com/ru/articles/554404/< br />
https://en.wikipedia.org/wiki/British_Post_Office_scandal

bgfx Emscripten アプリケーションの構築

この投稿では、Emscripten を使用して Web (WebAssembly) 用の bgfx アプリケーションを構築する方法について説明します。

インストール プラットフォームは、Arch Linux などの Linux x86-64 です。

まず、Emscripten バージョン 3.1.51 をインストールしましょう。そうでないと、Emscripten の最新バージョンではダイナミック ライブラリのタイプが変更されているため、成功しません。詳細はこちらでご覧いただけます:
https://github.com/bkaradzic/bgfx/Discussions/3266

これは次のように行われます:


git clone https://github.com/emscripten-core/emsdk.git



cd emsdk



./emsdk install 3.1.51



./emsdk activate 3.1.51



source ./emsdk_env.sh



WebAssembly 用に bgfx をアセンブルしましょう –エムスクリプト:


mkdir bgfx-build-test



cd bgfx-build-test



git clone https://github.com/bkaradzic/bx.git



git clone https://github.com/bkaradzic/bimg.git



git clone https://github.com/bkaradzic/bgfx.git



cd bgfx



emmake make wasm-debug



その結果、.build フォルダーには .bc 拡張子の付いたビットコード ファイルが作成されます。これは、bgfx アプリケーションにリンクする必要があります。
bgfx.bc、bx.bc、bimg.bc である必要があります。アセンブリが異なると、アセンブリのタイプ (リリース/デバッグ) に応じて、これらのファイルの名前も異なります。

.bc ファイルを含む CMakeLists.txt ファイルへのリンク (たとえば、bgfx-experiments プロジェクトのファイルへの絶対パス) を追加します。


target_link_libraries(${PROJECT_NAME} SDL2 GL /home/demensdeum_stream/Sources/bgfx-build/bgfx/.build/wasm/bin/bgfxDebug.bc /home/demensdeum_stream/Sources/bgfx-build/bgfx/.build/wasm/bin/bxDebug.bc /home/demensdeum_stream/Sources/bgfx-build/bgfx/.build/wasm/bin/bimgDebug.bc)



次に、プラットフォーム データのネイティブ ウィンドウ ハンドルを bgfx 初期化に変更します。


bgfx::PlatformData platformData{};



platformData.context = NULL;



platformData.backBuffer = NULL;



platformData.backBufferDS = NULL;



platformData.nwh = (void*)"#canvas";



レンダリング タイプを OpenGL に変更する必要もあります。


bgfx::Init init;



init.type = bgfx::RendererType::OpenGL;







init.resolution.width = screenWidth;



init.resolution.height = screenHeight;



init.resolution.reset = BGFX_RESET_VSYNC;



init.platformData = platformData;







if (!bgfx::init(init))



{



    throw std::runtime_error("Failed to initialize bgfx");



}



GLSL シェーダを 120 に再コンパイルします:


shaderc -f "VertexShader.vs" -o "VertexShader.glsl" --type "v" -p "120"



shaderc -f "FragmentShader.fs" -o "FragmentShader.glsl" --type "f" -p "120"



はい、ただし、.glsl ファイルは –preload-file: として CMakeLists.txt に追加する必要があります:


set(CMAKE_CXX_FLAGS ... <Остальная часть>



--preload-file VertexShader.glsl \



--preload-file FragmentShader.glsl \



アプリケーションのメイン レンダー ループを、emscripten_set_main_loop による while 関数呼び出しに置き換える作業が残ります。

これについてはここで読むことができます:
https ://demensdeum.com/blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/

次に、通常どおり Emscripten プロジェクトをアセンブルします。すべてが機能するはずです。
興味深い – Emscripten 3.1.51 ビルドには OpenAL (または私だけ) が欠けているようです。

bgfx と Emscripten で正しくコンパイルされるプロジェクトのソース コード:
https://github.com/demensdeum/ bgfx-experiments/tree/main/2-emscripten-build

ソース

https://github.com/bkaradzic/bgfx/Discussions/3266
https://bkaradzic.github.io/bgfx/build.html
https://emscripten.org/docs/getting_started/downloads.html
https ://demensdeum.com/blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/
https://llvm.org/docs/BitCodeFormat.html

Surreal Engine C++ を WebAssembly に移植

この投稿では、Surreal Engine ゲーム エンジンを WebAssembly に移植する方法について説明します。

シュール エンジン – Unreal Engine 1 の機能のほとんどを実装するゲーム エンジン、このエンジン上の有名なゲーム -アンリアル トーナメント 99、アンリアル、デウス エクス、アンダイング。これは、主にシングルスレッド実行環境で動作する古典的なエンジンを指します。

当初、私は、妥当な時間内に完了できないプロジェクトに挑戦して、Twitch フォロワーに私でも実行できないプロジェクトがあることを示すことを考えていました。最初のストリーム中に、Emscripten を使用して Surreal Engine C++ を WebAssembly に移植するタスクが実行可能であることに突然気づきました。

Surreal Engine Emscripten Demo

1 か月後には、WebAssembly でフォークとエンジンのアセンブリをデモンストレーションできるようになります。
https://demensdeum.com/demos/SurrealEngine/

オリジナルと同様に、コントロールはキーボードの矢印を使用して実行されます。次に、これをモバイル コントロール (タチ) に適応させ、Unreal Championship 99 レンダリングの正しいライティングやその他のグラフィック機能を追加する予定です。

どこから始めればよいですか?

最初に言いたいのは、Emscripten を使用すると、どのプロジェクトでも C++ から WebAssembly に移植できるということです。唯一の問題は、機能がどの程度完成するかということです。 Surreal Engine の場合は、ライブラリ ポートがすでに Emscripten で利用できるプロジェクトを選択してください。エンジンは SDL 2、OpenAL – ライブラリを使用します。どちらも Emscripten に移植されています。ただし、Vulkan はグラフィックス API として使用されており、現在 HTML5 では利用できません。WebGPU の実装作業が進行中ですが、これもドラフト段階にあり、Vulkan から WebGPU へのさらなる移植がどれほど簡単になるかも不明です。完全に標準化された後。したがって、Surreal Engine 用に独自の基本的な OpenGL-ES / WebGL レンダリングを作成する必要がありました。

プロジェクトのビルド

Surreal Engine でシステムを構築 – CMake も移植を簡素化します。 Emscripten はネイティブ ビルダーを提供します。えむけ、えむけ

Surreal Engine の移植は、Death-Mask と呼ばれる、WebGL/OpenGL ES および C++ で作成した最新ゲームのコードに基づいていました。そのため、開発ははるかに簡単で、必要なビルド フラグとコード サンプルがすべて揃っていました。

CMakeLists.txt の最も重要なポイントの 1 つは Emscripten のビルド フラグです。以下はプロジェクト ファイルの例です。


-s MAX_WEBGL_VERSION=2 \

-s EXCEPTION_DEBUG \

-fexceptions \

--preload-file UnrealTournament/ \

--preload-file SurrealEngine.pk3 \

--bind \

--use-preload-plugins \

-Wall \

-Wextra \

-Werror=return-type \

-s USE_SDL=2 \

-s ASSERTIONS=1 \

-w \

-g4 \

-s DISABLE_EXCEPTION_CATCHING=0 \

-O3 \

--no-heap-copy \

-s ALLOW_MEMORY_GROWTH=1 \

-s EXIT_RUNTIME=1")

ビルド スクリプト自体:


emmake make -j 16

cp SurrealEngine.data /srv/http/SurrealEngine/SurrealEngine.data

cp SurrealEngine.js /srv/http/SurrealEngine/SurrealEngine.js

cp SurrealEngine.wasm /srv/http/SurrealEngine/SurrealEngine.wasm

cp ../buildScripts/Emscripten/index.html /srv/http/SurrealEngine/index.html

cp ../buildScripts/Emscripten/background.png /srv/http/SurrealEngine/background.png

次に、インデックスを準備します。 .html 、プロジェクト ファイル システム プリローダーが含まれています。 Web にアップロードするために、Unreal Championship Demo バージョン 338 を使用しました。CMake ファイルからわかるように、解凍されたゲーム フォルダーがビルド ディレクトリに追加され、Emscripten のプリロード ファイルとしてリンクされました。

メインコードの変更

その後、ゲームのゲーム ループを変更する必要がありました。無限ループを実行することはできません。これによりブラウザがフリーズします。代わりに emscripten_set_main_loop を使用する必要があります。この機能については 2017 年のメモ「< a href="https://demensdeum.com /blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">SDL C++ ゲームを HTML5 (Emscripten) に移植」
while ループを終了するコードを if に変更し、ゲーム ループを含むゲーム エンジンのメイン クラスをグローバル スコープに表示し、グローバル オブジェクトからゲーム ループ ステップを呼び出すグローバル関数を作成します。 :


#include <emscripten.h>

Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;

void emscripten_game_loop_step() {

	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();

}

#endif

この後、アプリケーションにバックグラウンド スレッドがないことを確認する必要があります。存在する場合は、シングル スレッド実行用に書き直すか、Emscripten の phtread ライブラリを使用する準備をします。
Surreal Engine のバックグラウンド スレッドは音楽の再生に使用されます。データは、現在のトラック、音楽を再生する必要性、またはその不在に関するメイン エンジン スレッドから取得され、バックグラウンド スレッドはミューテックス経由で新しい状態を受け取り、新しい音楽の再生を開始します。 、または一時停止します。バックグラウンド ストリームは、再生中に音楽をバッファリングするためにも使用されます。
pthread を使用して Emscripten 用の Surreal Engine を構築しようとした試みは失敗しました。SDL2 ポートと OpenAL ポートは pthread サポートなしで構築されており、音楽のために再構築したくなかったためです。そこで、BGM ストリームの機能をループを使用したシングルスレッド実行に移行しました。 C++ コードから pthread 呼び出しを削除することで、バッファリングと音楽再生をメイン スレッドに移動し、遅延が発生しないように、バッファを数秒増やしました。

次に、グラフィックとサウンドの具体的な実装について説明します。

Vulkan はサポートされていません!

はい、Vulkan は HTML5 ではサポートされていませんが、すべてのマーケティング パンフレットでは Vulkan の主な利点としてクロスプラットフォームおよび広範なプラットフォームのサポートが紹介されています。このため、簡素化された OpenGL タイプの基本的なグラフィックス レンダラーを独自に作成する必要がありました。 ES はモバイル デバイスで使用され、最新の OpenGL のファッショナブルな機能が含まれていない場合もありますが、WebGL に非常によく移植されており、まさに Emscripten が実装しているものです。基本的なタイル レンダリング、最も単純な GUI 表示用の bsp レンダリング、およびモデルとマップのレンダリングの作成は 2 週間で完了しました。おそらくこれがこのプロジェクトで最も困難な部分でした。 Surreal Engine レンダリングの全機能を実装するには、まだ多くの作業が必要です。そのため、読者からのコードやプル リクエストの形での支援を歓迎します。

OpenAL がサポートされています!

幸運なことに、Surreal Engine はオーディオ出力に OpenAL を使用しています。 OpenAL で簡単な Hello World を記述し、Emscripten を使用して WebAssembly で組み立てたので、すべてがいかに単純であるかが明らかになり、サウンドの移植に着手しました。
数時間のデバッグ後、Emscripten の OpenAL 実装にはいくつかのバグがあることが明らかになりました。たとえば、モノラル チャネル数の読み取りを初期化するときに、メソッドが無限の数を返し、無限サイズのベクトルを初期化しようとした後、C++例外vector::length_errorでクラッシュします。
>
モノラル チャンネルの数を 2048 にハードコーディングすることで、この問題を回避することができました。


		alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);



#if __EMSCRIPTEN__

		monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?

#endif



ネットワークはありますか?

Surreal Engine は現在オンライン プレイをサポートしていません。ボットでのプレイはサポートされていますが、これらのボット用の AI を作成する人が必要です。理論的には、Websocket を使用して WebAssembly/Emscripten にネットワーク ゲームを実装できます。

結論

結論として、Emscripten ポートがあるライブラリの使用と、WebAssembly 用に C++ でゲームを実装した過去の経験のおかげで、Surreal Engine の移植は非常にスムーズに完了したと言いたいと思います。エムスクリプテンで。以下は、このトピックに関する知識源とリポジトリへのリンクです。
マ、マ、マ、モンスターを倒せ!

また、できれば WebGL/OpenGL ES レンダリング コードでプロジェクトを支援したい場合は、Telegram で私に連絡してください。
https://t.me/demenscave

リンク

https://demensdeum.com/demos/Sur​​realEngine/
https://github.com/demensdeum/SurrealEngine-Emscripten

https://github.com/dpjudas/SurrealEngine

Flash Forever – Interceptor 2021

Recently, it turned out that Adobe Flash works quite stably under Wine. During a 4-hour stream, I made the game Interceptor 2021, which is a sequel to the game Interceptor 2020, written for the ZX Spectrum.

For those who are not in the know – the Flash technology provided interactivity on the web from 2000 to around 2015. Its shutdown was prompted by an open letter from Steve Jobs, in which he wrote that Flash should be consigned to history because it lagged on the iPhone. Since then, JS has become even more sluggish than Flash, and Flash itself has been wrapped in JS, making it possible to run it on anything thanks to the Ruffle player.

You can play it here:
https://demensdeum.com/demos/Interceptor2021

Video:
https://www.youtube.com/watch?v=-3b5PkBvHQk

Source code:
https://github.com/demensdeum/Interceptor-2021

メイソンズ-DR デモ ゲーム

Masonry-AR は、現実世界で街を移動し、本からフリーメーソンの知識を収集し、通貨を入手してフリーメーソン教団の領土を占領する必要がある拡張現実ゲームです。このゲームは実際の組織とは何の関係もありません。すべての試合はランダムです。

ゲームデモ:
https://demensdeum.com/demos/masonry-ar/client

ヴィッキー:
https://demensdeum.com/masonry-ar-wiki-ru/

ソースコード:
https://github.com/demensdeum/Masonry-AR

CRUDリポジトリ

このノートでは、よく知られている古典的な CRUD パターン、Swift 言語での実装の基本原則について説明します。 Swift は、Windows、Linux、macOS、iOS、Android で利用できるオープンなクロスプラットフォーム言語です。

データ ストレージとアプリケーション ロジックを抽象化するためのソリューションは数多くあります。そのようなソリューションの 1 つが CRUD アプローチです。これは C– の頭字語です。作成、R -読み取り、U –最新情報、D–削除
します。通常、この原則はデータベースへのインターフェイスの実装を通じて実装され、要素は id などの一意の識別子を使用して操作されます。 CRUD – 文字ごとにインターフェイスが作成されます。作成(オブジェクト、ID)、読み取り(ID)、更新(オブジェクト、ID)、削除(オブジェクト、ID)。
オブジェクトの内部に ID が含まれている場合、オブジェクト全体がフィールド – とともに渡されるため、メソッド (Create、Update、Delete) から id 引数を省略できます。 ID。しかし、– についてはID によってデータベースからオブジェクトを取得したいため、読み取りには ID が必要です。

名前はすべて架空のものです

仮想の AssistantAI アプリケーションが無料の EtherRelm データベース SDK を使用して作成され、統合は簡単で、API は非常に便利で、その結果、アプリケーションが市場にリリースされたと想像してみましょう。
突然、SDK 開発者の EtherRelm は有料化を決定し、アプリケーション ユーザーあたりの価格を年間 100 ドルに設定しました。
何?はい! AssistantAI の開発者は、すでに 100 万人のアクティブ ユーザーを抱えているため、今何をすべきでしょうか。 1億ドル支払う?
代わりに、プラットフォーム固有の RootData データベースへのアプリケーションの転送を評価する決定が行われます。プログラマーによると、そのような転送には約 6 か月かかりますが、これにはアプリケーションの新機能の実装は考慮されていません。いくつか考えた結果、アプリケーションを市場から削除し、BueMS データベースが組み込まれた別の無料のクロスプラットフォーム フレームワークに書き直すことが決定されました。これにより、有料データベースの問題が解決され、他のプラットフォームでの開発が簡素化されます。
1 年後、アプリケーションは BueMS で書き直されましたが、突然フレームワーク開発者が有料化を決定しました。チームは同じ罠に 2 度陥ったことが判明しました。2 度目から抜け出せるかどうかはまったく別の話です。

抽象化が助けになります

開発者がアプリケーション内でインターフェイスの抽象化を使用していれば、これらの問題は回避できたはずです。 OOPの3つの柱へ–ポリモーフィズム、カプセル化、継承、少し前に別の – が追加されました。抽象化
します。データ抽象化により、ビジネス上の問題を解決するために使用される特定の実装を実装するのに十分な正確さを保ちながら、アイデアやモデルを最小限の詳細で一般的な用語で記述することができます。
アプリケーションロジックが依存しないようにデータベース操作を抽象化するにはどうすればよいでしょうか?私たちは CRUD アプローチを使用しています!

簡略化された UML CRUD 図は次のようになります。

架空の EtherRelm データベースの例:

実際の SQLite データベースの例:

すでにお気づきのとおり、データベースを切り替えると、アプリケーションがやり取りする CRUD インターフェイスのみが変更され、変更されません。 CRUD は GoF パターンの実装です。アダプターなので、これを使用して、アプリケーション インターフェイスをあらゆるデータベースに適合させ、互換性のないインターフェイスを結合します。
言葉は空です、コードを見せてください
プログラミング言語で抽象化を実装するには、インターフェイス/プロトコル/抽象クラスが使用されます。これらはすべて同じ程度の現象ですが、インタビュー中にそれらの違いを挙げるように求められることがありますが、個人的にはこれはあまり意味がないと考えています。使用の唯一の目的はデータ抽象化を実装することです。それ以外の場合は、インタビュー対象者の記憶をテストすることです。
CRUD は多くの場合、リポジトリ パターンのフレームワーク内で実装されますが、リポジトリは CRUD インターフェイスを実装する場合と実装しない場合があり、すべて開発者の創意工夫に依存します。

UserDefaults データベースを直接操作する、Book 構造リポジトリの非常に典型的な Swift コードを考えてみましょう。


struct Book: Codable {
	let title: String
	let author: String
}

class BookRepository {
	func save(book: Book) {
    		let record = try! JSONEncoder().encode(book)
    		UserDefaults.standard.set(record, forKey: book.title)
	}
    
	func get(bookWithTitle title: String) -> Book? {
    		guard let data = UserDefaults.standard.data(forKey: title) else { return nil }
    		let book = try! JSONDecoder().decode(Book.self, from: data)
    		return book
	}
    
	func delete(book: Book) {
    		UserDefaults.standard.removeObject(forKey: book.title)
	}
}

let book = Book(title: "Fear and Loathing in COBOL", author: "Sir Edsger ZX Spectrum")
let repository = BookRepository()
repository.save(book: book)
print(repository.get(bookWithTitle: book.title)!)
repository.delete(book: book)
guard repository.get(bookWithTitle: book.title) == nil else {
	print("Error: can't delete Book from repository!")
	exit(1)
}

上記のコードは単純に見えますが、DRY (Do notrepeat Yourself) 原則の違反の数とコードの一貫性を数えてみましょう。
UserDefaults データベースへの接続
JSON エンコーダーおよびデコーダーとの接続 – #8211; JSONエンコーダ、JSONデコーダ
Book 構造に接続されていますが、データベースに保存する構造ごとにリポジトリ クラスを作成しないように抽象リポジトリが必要です (DRY 違反)

この種の CRUD リポジトリ コードはよく見かけます。使用することはできますが、コードの高度な結合と重複により、時間の経過とともにそのサポートが非常に複雑になるという事実につながります。これは、別のデータベースに切り替えようとする場合、またはアプリケーションで作成されたすべてのリポジトリでデータベースを操作する内部ロジックを変更する場合に特に顕著になります。
コードを複製する代わりに、カップリングを高く保ちます。 CRUD リポジトリのプロトコルを作成して、データベース インターフェイスとアプリケーション ビジネス ロジックを抽象化し、DRY を尊重し、低結合を実装しましょう。

    typealias Item = Codable
    typealias ItemIdentifier = String
    
    func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws
    func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T
    func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws
    func delete(id: CRUDRepository.ItemIdentifier) async throws
}

CRUDRepository プロトコルは、特定の CRUD リポジトリをさらに実装するためのインターフェイスと関連するデータ型を記述します。

次に、UserDefaults データベースの特定の実装を作成します。

    private typealias RecordIdentifier = String
    
    let tableName: String
    let dataTransformer: DataTransformer
    
    init(
   	 tableName: String = "",
   	 dataTransformer: DataTransformer = JSONDataTransformer()
    ) {
   	 self.tableName = tableName
   	 self.dataTransformer = dataTransformer
    }
    
    private func key(id: CRUDRepository.ItemIdentifier) -> RecordIdentifier {
   	 "database_\(tableName)_item_\(id)"
    }
   	 
    private func isExists(id: CRUDRepository.ItemIdentifier) async throws -> Bool {
   	 UserDefaults.standard.data(forKey: key(id: id)) != nil
    }
    
    func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 let data = try await dataTransformer.encode(item)
   	 UserDefaults.standard.set(data, forKey: key(id: id))
   	 UserDefaults.standard.synchronize()
    }
    
    func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T {
   	 guard let data = UserDefaults.standard.data(forKey: key(id: id)) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let item: T = try await dataTransformer.decode(data: data)
   	 return item
    }
    
    func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let data = try await dataTransformer.encode(item)
   	 UserDefaults.standard.set(data, forKey: key(id: id))
   	 UserDefaults.standard.synchronize()
    }
    
    func delete(id: CRUDRepository.ItemIdentifier) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 UserDefaults.standard.removeObject(forKey: key(id: id))
   	 UserDefaults.standard.synchronize()
    }
}

コードは長く見えますが、疎結合を含む CRUD リポジトリの完全な具体的な実装が含まれています。詳細は以下で説明します。
コードの自己文書化のために typealias が追加されました。
弱い結合と強い結合
について。特定の構造 (struct) からの切り離しは、汎用 T を使用して実装されます。これには、Codable プロトコルを実装する必要があります。 Codable では、TopLevelEncoder および TopLevelDecoder プロトコルを実装するクラス (JSONEncoder や JSONDecoder など) を使用して構造を変換できます。基本型 (Int、String、Float など) を使用する場合、構造を変換するために追加のコードを記述する必要はありません。 p>

特定のエンコーダとデコーダからの分離は、DataTransformer プロトコルの抽象化を使用して行われます。

	func encode<T: Encodable>(_ object: T) async throws -> Data
	func decode<T: Decodable>(data: Data) async throws -> T
}

データ トランスフォーマーの実装を使用して、エンコーダーとデコーダーのインターフェイスの抽象化を実装し、さまざまなタイプのデータ形式で確実に動作するように疎結合を実装しました。

以下は、特定の DataTransformer、つまり JSON のコードです。

	func encode<T>(_ object: T) async throws -> Data where T : Encodable {
    		let data = try JSONEncoder().encode(object)
    		return data
	}
    
	func decode<T>(data: Data) async throws -> T where T : Decodable {
    		let item: T = try JSONDecoder().decode(T.self, from: data)
    		return item
	}
}

それは可能でしたか?

何が変わりましたか?今後は、Codable プロトコルを実装する構造を操作できるように特定のリポジトリを初期化するだけで十分です。そのため、コードを複製する必要がなく、アプリケーションの疎結合を実装できます。

特定のリポジトリを使用したクライアント CRUD の例。UserDefaults はデータベース、JSON データ形式、クライアント構造であり、配列の書き込みと読み取りの例でもあります。


print("One item access example")

do {
	let clientRecordIdentifier = "client"
	let clientOne = Client(name: "Chill Client")
	let repository = UserDefaultsRepository(
    	tableName: "Clients Database",
    	dataTransformer: JSONDataTransformer()
	)
	try await repository.create(id: clientRecordIdentifier, item: clientOne)
	var clientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print("Client Name: \(clientRecord.name)")
	clientRecord.name = "Busy Client"
	try await repository.update(id: clientRecordIdentifier, item: clientRecord)
	let updatedClient: Client = try await repository.read(id: clientRecordIdentifier)
	print("Updated Client Name: \(updatedClient.name)")
	try await repository.delete(id: clientRecordIdentifier)
	let removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print(removedClientRecord)
}
catch {
	print(error.localizedDescription)
}

print("Array access example")

let clientArrayRecordIdentifier = "clientArray"
let clientOne = Client(name: "Chill Client")
let repository = UserDefaultsRepository(
	tableName: "Clients Database",
	dataTransformer: JSONDataTransformer()
)
let array = [clientOne]
try await repository.create(id: clientArrayRecordIdentifier, item: array)
let savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)
print(savedArray.first!)

最初の CRUD チェック中に例外処理が実装されており、リモート アイテムの読み取りは利用できなくなります。

データベースの切り替え

次に、現在のコードを別のデータベースに転送する方法を説明します。たとえば、ChatGPT が生成した SQLite リポジトリ コードを取り上げます。


class SQLiteRepository: CRUDRepository {
    private typealias RecordIdentifier = String
    
    let tableName: String
    let dataTransformer: DataTransformer
    private var db: OpaquePointer?

    init(
   	 tableName: String,
   	 dataTransformer: DataTransformer = JSONDataTransformer()
    ) {
   	 self.tableName = tableName
   	 self.dataTransformer = dataTransformer
   	 self.db = openDatabase()
   	 createTableIfNeeded()
    }
    
    private func openDatabase() -> OpaquePointer? {
   	 var db: OpaquePointer? = nil
   	 let fileURL = try! FileManager.default
   		 .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
   		 .appendingPathComponent("\(tableName).sqlite")
   	 if sqlite3_open(fileURL.path, &db) != SQLITE_OK {
   		 print("error opening database")
   		 return nil
   	 }
   	 return db
    }
    
    private func createTableIfNeeded() {
   	 let createTableString = """
   	 CREATE TABLE IF NOT EXISTS \(tableName) (
   	 id TEXT PRIMARY KEY NOT NULL,
   	 data BLOB NOT NULL
   	 );
   	 """
   	 var createTableStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, createTableString, -1, &createTableStatement, nil) == SQLITE_OK {
   		 if sqlite3_step(createTableStatement) == SQLITE_DONE {
       		 print("\(tableName) table created.")
   		 } else {
       		 print("\(tableName) table could not be created.")
   		 }
   	 } else {
   		 print("CREATE TABLE statement could not be prepared.")
   	 }
   	 sqlite3_finalize(createTableStatement)
    }
    
    private func isExists(id: CRUDRepository.ItemIdentifier) async throws -> Bool {
   	 let queryStatementString = "SELECT data FROM \(tableName) WHERE id = ?;"
   	 var queryStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
   		 sqlite3_bind_text(queryStatement, 1, id, -1, nil)
   		 if sqlite3_step(queryStatement) == SQLITE_ROW {
       		 sqlite3_finalize(queryStatement)
       		 return true
   		 } else {
       		 sqlite3_finalize(queryStatement)
       		 return false
   		 }
   	 } else {
   		 print("SELECT statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
    }
    
    func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 let insertStatementString = "INSERT INTO \(tableName) (id, data) VALUES (?, ?);"
   	 var insertStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
   		 let data = try await dataTransformer.encode(item)
   		 sqlite3_bind_text(insertStatement, 1, id, -1, nil)
   		 sqlite3_bind_blob(insertStatement, 2, (data as NSData).bytes, Int32(data.count), nil)
   		 if sqlite3_step(insertStatement) == SQLITE_DONE {
       		 print("Successfully inserted row.")
   		 } else {
       		 print("Could not insert row.")
       		 throw CRUDRepositoryError.databaseError
   		 }
   	 } else {
   		 print("INSERT statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(insertStatement)
    }
    
    func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T {
   	 let queryStatementString = "SELECT data FROM \(tableName) WHERE id = ?;"
   	 var queryStatement: OpaquePointer? = nil
   	 var item: T?
   	 if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
   		 sqlite3_bind_text(queryStatement, 1, id, -1, nil)
   		 if sqlite3_step(queryStatement) == SQLITE_ROW {
       		 let queryResultCol1 = sqlite3_column_blob(queryStatement, 0)
       		 let queryResultCol1Length = sqlite3_column_bytes(queryStatement, 0)
       		 let data = Data(bytes: queryResultCol1, count: Int(queryResultCol1Length))
       		 item = try await dataTransformer.decode(data: data)
   		 } else {
       		 throw CRUDRepositoryError.recordNotFound(id: id)
   		 }
   	 } else {
   		 print("SELECT statement could not be prepared")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(queryStatement)
   	 return item!
    }
    
    func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let updateStatementString = "UPDATE \(tableName) SET data = ? WHERE id = ?;"
   	 var updateStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, updateStatementString, -1, &updateStatement, nil) == SQLITE_OK {
   		 let data = try await dataTransformer.encode(item)
   		 sqlite3_bind_blob(updateStatement, 1, (data as NSData).bytes, Int32(data.count), nil)
   		 sqlite3_bind_text(updateStatement, 2, id, -1, nil)
   		 if sqlite3_step(updateStatement) == SQLITE_DONE {
       		 print("Successfully updated row.")
   		 } else {
       		 print("Could not update row.")
       		 throw CRUDRepositoryError.databaseError
   		 }
   	 } else {
   		 print("UPDATE statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(updateStatement)
    }
    
    func delete(id: CRUDRepository.ItemIdentifier) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let deleteStatementString = "DELETE FROM \(tableName) WHERE id = ?;"
   	 var deleteStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, deleteStatementString, -1, &deleteStatement, nil) == SQLITE_OK {
   		 sqlite3_bind_text(deleteStatement, 1, id, -1, nil)
   		 if sqlite3_step(deleteStatement) == SQLITE_DONE {
       		 print("Successfully deleted row.")
   		 } else {
       		 print("Could not delete row.")
       		 throw CRUDRepositoryError.databaseError
   		 }
   	 } else {
   		 print("DELETE statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(deleteStatement)
    }
}

または、同じく ChatGPT によって生成された、ファイル システムのリポジトリの CRUD コード:


class FileSystemRepository: CRUDRepository {
	private typealias RecordIdentifier = String
    
	let directoryName: String
	let dataTransformer: DataTransformer
	private let fileManager = FileManager.default
	private var directoryURL: URL
    
	init(
    	directoryName: String = "Database",
    	dataTransformer: DataTransformer = JSONDataTransformer()
	) {
    	self.directoryName = directoryName
    	self.dataTransformer = dataTransformer
   	 
    	let paths = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
    	directoryURL = paths.first!.appendingPathComponent(directoryName)
   	 
    	if !fileManager.fileExists(atPath: directoryURL.path) {
        	try? fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
    	}
	}
    
	private func fileURL(id: CRUDRepository.ItemIdentifier) -> URL {
    	return directoryURL.appendingPathComponent("item_\(id).json")
	}
    
	private func isExists(id: CRUDRepository.ItemIdentifier) async throws -> Bool {
    	return fileManager.fileExists(atPath: fileURL(id: id).path)
	}
    
	func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
    	let data = try await dataTransformer.encode(item)
    	let url = fileURL(id: id)
    	try data.write(to: url)
	}
    
	func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T {
    	let url = fileURL(id: id)
    	guard let data = fileManager.contents(atPath: url.path) else {
        	throw CRUDRepositoryError.recordNotFound(id: id)
    	}
    	let item: T = try await dataTransformer.decode(data: data)
    	return item
	}
    
	func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
    	guard try await isExists(id: id) else {
        	throw CRUDRepositoryError.recordNotFound(id: id)
    	}
    	let data = try await dataTransformer.encode(item)
    	let url = fileURL(id: id)
    	try data.write(to: url)
	}
    
	func delete(id: CRUDRepository.ItemIdentifier) async throws {
    	guard try await isExists(id: id) else {
        	throw CRUDRepositoryError.recordNotFound(id: id)
    	}
    	let url = fileURL(id: id)
    	try fileManager.removeItem(at: url)
	}
}

クライアント コード内のリポジトリを置き換えます。


print("One item access example")

do {
	let clientRecordIdentifier = "client"
	let clientOne = Client(name: "Chill Client")
	let repository = FileSystemRepository(
    	directoryName: "Clients Database",
    	dataTransformer: JSONDataTransformer()
	)
	try await repository.create(id: clientRecordIdentifier, item: clientOne)
	var clientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print("Client Name: \(clientRecord.name)")
	clientRecord.name = "Busy Client"
	try await repository.update(id: clientRecordIdentifier, item: clientRecord)
	let updatedClient: Client = try await repository.read(id: clientRecordIdentifier)
	print("Updated Client Name: \(updatedClient.name)")
	try await repository.delete(id: clientRecordIdentifier)
	let removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print(removedClientRecord)
}
catch {
	print(error.localizedDescription)
}

print("Array access example")

let clientArrayRecordIdentifier = "clientArray"
let clientOne = Client(name: "Chill Client")
let repository = FileSystemRepository(
	directoryName: "Clients Database",
	dataTransformer: JSONDataTransformer()
)
let array = [clientOne]
try await repository.create(id: clientArrayRecordIdentifier, item: array)
let savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)
print(savedArray.first!)

UserDefaultsRepository の初期化は、適切な引数を備えた FileSystemRepository に置き換えられました。
クライアント コードの 2 番目のバージョンを実行すると、ドキュメント フォルダーに「Clients Database」ディレクトリが表示されます。このディレクトリには、1 つのクライアント構造を持つ JSON でシリアル化された配列のファイルが含まれています。

データ保存形式の切り替え

次に、ChatGPT に XML のエンコーダーとデコーダーを生成するよう依頼しましょう。

	let formatExtension = "xml"
    
	func encode<T: Encodable>(_ item: T) async throws -> Data {
    	let encoder = PropertyListEncoder()
    	encoder.outputFormat = .xml
    	return try encoder.encode(item)
	}
    
	func decode<T: Decodable>(data: Data) async throws -> T {
    	let decoder = PropertyListDecoder()
    	return try decoder.decode(T.self, from: data)
	}
}

Swift の組み込み型のおかげで、ニューラル ネットワークのタスクが初歩的になります。

クライアント コードで JSON を XML に置き換えます。


print("One item access example")

do {
	let clientRecordIdentifier = "client"
	let clientOne = Client(name: "Chill Client")
	let repository = FileSystemRepository(
    	directoryName: "Clients Database",
    	dataTransformer: XMLDataTransformer()
	)
	try await repository.create(id: clientRecordIdentifier, item: clientOne)
	var clientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print("Client Name: \(clientRecord.name)")
	clientRecord.name = "Busy Client"
	try await repository.update(id: clientRecordIdentifier, item: clientRecord)
	let updatedClient: Client = try await repository.read(id: clientRecordIdentifier)
	print("Updated Client Name: \(updatedClient.name)")
	try await repository.delete(id: clientRecordIdentifier)
	let removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print(removedClientRecord)
}
catch {
	print(error.localizedDescription)
}

print("Array access example")

let clientArrayRecordIdentifier = "clientArray"
let clientOne = Client(name: "Chill Client")
let repository = FileSystemRepository(
	directoryName: "Clients Database",
	dataTransformer: XMLDataTransformer()
)
let array = [clientOne]
try await repository.create(id: clientArrayRecordIdentifier, item: array)
let savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)
print(savedArray.first!)

クライアント コードは 1 つの式 JSONDataTransformer -> のみに変更されました。 XMLDataTransformer

合計

CRUD リポジトリは、アプリケーション アーキテクチャ コンポーネントの疎結合を実装するために使用できる設計パターンの 1 つです。もう 1 つの解決策 – ORM (Object-Relational Mapping) を使用すると、ORM は構造をデータベースに完全にマッピングし、モデルによる変更をデータベース上に表示 (マッピング (!)) するアプローチを使用します。
しかし、それはまったく別の話です。

Swift 用の CRUD リポジトリの完全な実装は、次の場所から入手できます。
https://gitlab.com/demensdeum/crud-example

ところで、Swift は macOS 以外でも長い間サポートされてきましたが、この記事のコードはすべて Arch Linux 上で作成され、テストされました。

ソース

https://developer.apple.com/documentation/combine/topleveldecoder
https://developer.apple.com/documentation/combine/toplevelencoder
https://en.wikipedia.org/wiki/Create,_read,_update_and_delete

dd 入出力エラー

Linux で dd を使用して通常のディスクをコピーするときに入出力エラーが発生した場合はどうすればよいですか?

シトゥエヴィナは非常に悲しいですが、解決可能です。おそらく、使用、書き込み、読み取りができなくなった不良ブロックを含む故障したディスクを扱っていると考えられます。

そのようなディスクは必ず S.M.A.R.T. を使用してチェックしてください。ディスク エラーが表示される可能性が高くなります。私の場合はこれに当てはまりました。不良ブロックの数が非常に多かったので、古いハードドライブに別れを告げて、新しい SSD に交換する必要がありました。

問題は、このディスクには、作業に必要なライセンスされたソフトウェアを備えた完全に動作するシステムが含まれていることです。 partimage を使用してデータをすばやくコピーしようとしましたが、ユーティリティがディスクの 3 分の 1 だけをコピーし、セグメンテーション違反かその他の面白い Sishny/Sipplusplus ジョークで終了することに突然気づきました。

次に、ddを使用してデータをコピーしようとしましたが、ddがpartimageとほぼ同じ場所に到達し、入出力エラーが発生することがわかりました。同時に、conv=noerr、skip などのあらゆる種類の面白いフラグはまったく役に立ちませんでした。

しかし、ddrescue という GNU ユーティリティを使用すると、データは問題なく別のディスクにコピーされました。

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

Большим плюсом ddrescue является наличие встроенного прогрессбара, поэтому не приходится костылять какие-то ухищрения навроде pv и всяких не особо красивых флажков dd. Также ddrescure показывает количество попыток прочитать данные; еще на вики написано что утилита обладает каким-то сверх алгоритмом для считывания поврежденных данных, оставим это на проверку людям которые любят ковыряться в исходниках, мы же не из этих да?

https://ru.wikipedia.org/wiki/Ddrescue
https://www.gnu.org/software/ddrescue/ddrescue_ru.html

チャットGPT

皆さん、こんにちは!この記事では、ChatGPT – について話したいと思います。 OpenAI の強力な言語モデリングは、テキスト処理のさまざまな問題の解決に役立ちます。このツールがどのように機能し、実際の状況でどのように使用できるかを説明します。始めましょう!

現時点では、ChatGPT はニューラル ネットワークに基づく世界最高の言語モデルの 1 つです。これは、開発者が自然言語を生成し、その中で人々とコミュニケーションできるインテリジェント システムを作成できるように支援することを目的として作成されました。

ChatGPT の主な利点の 1 つは、テキストを状況に応じてモデル化できることです。これは、モデルが以前の対話を考慮し、それを使用して状況をより正確に理解し、より自然な応答を生成することを意味します。

ChatGPT は、カスタマー サポートの自動化、チャットボットの作成、テキスト生成など、さまざまなタスクに使用できます。

ChatGPT の背後にあるニューラル ネットワークは、非常に正確な予測を保証するために、大量のテキストでトレーニングされました。これにより、モデルは対話をサポートし、質問に答えることができる自然なテキストを生成できるようになります。

ChatGPT を使用すると、自然言語で人々と対話できる独自のチャットボットやその他のインテリジェント システムを作成できます。これは、観光、小売、カスタマー サポートなどの業界で特に役立ちます。

結論として、ChatGPT –これは、さまざまな言語モデリングの問題を解決するための強力なツールです。コンテキスト モデリングの機能により、チャットボットやインテリジェント システムの作成に特に役立ちます。


実際、ChatGPT は上記のすべてを完全に単独で記述しました。何?はい?私自身もショックを受けています!

ここでグリッド自体を試すことができます。
https://chat.openai.com/chat

macOS で USB キーボードのバックライトをオンにする

最近、RGB バックライト付きの非常に安価な Getorix GK-45X USB キーボードを購入しました。 M1 プロセッサを搭載した MacBook Pro に接続してみたところ、RGB バックライトが機能しないことが明らかになりました。 Fn + Scroll Lock の魔法の組み合わせを押してもバックライトは点灯せず、MacBook 画面のバックライト レベルのみが変化しました。
この問題にはいくつかの解決策があります。OpenRGB (機能しない)、HID LED テスト (機能しない) です。 kvmswitch ユーティリティのみが機能しました:
https://github.com/stoutput/OSX-KVM

GitHub からダウンロードし、システム設定のセキュリティ パネルでターミナルからの実行を許可する必要があります。
説明からわかるように、ユーティリティを起動すると、Fn + Scroll Lock キーが送信され、キーボードのバックライトがオン/オフになります。

みつめが通る (NES) –ダンディの第三の目

https://www.youtube.com/watch?v=LT2U3CJnzxU

みつめが通る (三つ目とおる みつめが通る?、文字通り「three-eyed」) は、1992 年に夏目によって Nintendo Entertainment System 専用に開発されたプラットフォーム ビデオ ゲームです。このゲームは漫画およびアニメ『ミツメが通る』を原作としています。これは、ナツメによって開発された 2 番目のアニメベースのゲームです。前作は 2 年前に MSX プラットフォーム向けにリリースされた『みつめが通る: 3Lie-Mon』でした。ロシアでは、このゲームは「3 Eyes」または「3 Eyes」としてよく知られています。第三の目。

Number 2

Comrades, I take pride in projects that were created on the basis of Flame Steel Framework 1 and specifically on Flame Steel Engine 1, namely Death-Mask, Cube Art Project, since all this was conceived as a big experiment, creating a multimedia framework alone that can work on the most platforms. I think the experiment ended successfully immediately after the release of the Cube Art Project.

Now about the decisions that I came to during the development of new projects on FSFramework 1

During the development of Space Jaguar and the Space Jaguar Galaxy Bastards shooter, it became clear that the Flame Steel Framework tools were already outdated, not even having time to become at least somewhat convenient.

Therefore, I decided to develop a completely new Flame Steel Framework 2. The main decision will be to switch to my Rise 2 transpiler language, and the Component System (ECS) will no longer be used architecturally, because. it turned out to be needed only within the framework of game logic with great dynamics. For this reason, in Flame Steel Framework 2, the component system will only be possible while using the scripting languages ​​that are planned to be implemented (at least Lua and JavaScript), an interesting feature is that these languages ​​​​are dynamic in nature, so additional creation of the component system is redundant.

You can follow the development of new projects on the blog and on Gitlab:

https://gitlab.com/demensdeum/rise2

https://gitlab.com/demensdeum/flamesteelengine2

https://gitlab.com/demensdeum/flame-steel-engine-2-demo-projects

https://gitlab.com/demensdeum/space-jaguar-action-rpg

https://gitlab.com/demensdeum/space-jaguar-galaxy-bastards

ツリーソート

ツリーソート – 二分探索ツリーを使用したソート。時間計算量 – O(n²)。このようなツリーでは、左側の各ノードにはノードより小さい番号があり、右側にはノードより大きい番号があります。ルートから来て左から右に値を出力すると、ソートされた番号のリストが得られます。 。驚くべきことですね?

二分探索ツリー図を考えてみましょう。

デリック・クッツェー (パブリック ドメイン)

左下の各ノード、つまり右側のノードごとに、左下隅の最後から 2 番目の左ノードから数字を手動で読み取ってみてください。

次のようになります:

<オル>

  • 左下の最後から2番目のノード – 3.
  • 左分岐があります – 1.
  • この番号 (1) を選択してください
  • 次に頂点 3 (1, 3) を取得します
  • 右側はブランチ 6 ですが、ブランチが含まれています。したがって、同じように読みます。
  • ノード 6 の左分岐 4 (1、3、4)
  • ノード自体は 6 (1、3、4、6) です
  • 右 7 (1、3、4、6、7)
  • ルート ノードに移動します – 8 (1、3、4、6、7、8)
  • 類推により、右側にすべてを出力します
  • 最終的なリストを取得します – 1、3、4、6、7、8、10、13、14
  • コードでアルゴリズムを実装するには、次の 2 つの関数が必要です。

    <オル>

  • 二分探索ツリーの組み立て
  • 二分探索ツリーを正しい順序で出力する
  • 二分探索木は読み取られたときと同じ方法で組み立てられ、それが大きいか小さいかに応じて、左側または右側の各ノードに番号が付けられます。

    Lua での例:

    
    function Node:new(value, lhs, rhs)
        output = {}
        setmetatable(output, self)
        self.__index = self  
        output.value = value
        output.lhs = lhs
        output.rhs = rhs
        output.counter = 1
        return output  
    end
    
    function Node:Increment()
        self.counter = self.counter + 1
    end
    
    function Node:Insert(value)
        if self.lhs ~= nil and self.lhs.value > value then
            self.lhs:Insert(value)
            return
        end
    
        if self.rhs ~= nil and self.rhs.value < value then
            self.rhs:Insert(value)
            return
        end
    
        if self.value == value then
            self:Increment()
            return
        elseif self.value > value then
            if self.lhs == nil then
                self.lhs = Node:new(value, nil, nil)
            else
                self.lhs:Insert(value)
            end
            return
        else
            if self.rhs == nil then
                self.rhs = Node:new(value, nil, nil)
            else
                self.rhs:Insert(value)
            end
            return
        end
    end
    
    function Node:InOrder(output)
        if self.lhs ~= nil then
           output = self.lhs:InOrder(output)
        end
        output = self:printSelf(output)
        if self.rhs ~= nil then
            output = self.rhs:InOrder(output)
        end
        return output
    end
    
    function Node:printSelf(output)
        for i=0,self.counter-1 do
            output = output .. tostring(self.value) .. " "
        end
        return output
    end
    
    function PrintArray(numbers)
        output = ""
        for i=0,#numbers do
            output = output .. tostring(numbers[i]) .. " "
        end    
        print(output)
    end
    
    function Treesort(numbers)
        rootNode = Node:new(numbers[0], nil, nil)
        for i=1,#numbers do
            rootNode:Insert(numbers[i])
        end
        print(rootNode:InOrder(""))
    end
    
    
    numbersCount = 10
    maxNumber = 9
    
    numbers = {}
    
    for i=0,numbersCount-1 do
        numbers[i] = math.random(0, maxNumber)
    end
    
    PrintArray(numbers)
    Treesort(numbers)

    Важный нюанс что для чисел которые равны вершине придумано множество интересных механизмов подцепления к ноде, я же просто добавил счетчик к классу вершины, при распечатке числа возвращаются по счетчику.

    Ссылки

    https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/treesort

    Источники

    TreeSort Algorithm Explained and Implemented with Examples in Java | Sorting Algorithms | Geekific – YouTube

    Tree sort – YouTube

    Convert Sorted Array to Binary Search Tree (LeetCode 108. Algorithm Explained) – YouTube

    Sorting algorithms/Tree sort on a linked list – Rosetta Code

    Tree Sort – GeeksforGeeks

    Tree sort – Wikipedia

    How to handle duplicates in Binary Search Tree? – GeeksforGeeks

    Tree Sort | GeeksforGeeks – YouTube

    バケットソート

    バケット並べ替え – バケットごとに並べ替えます。このアルゴリズムはカウンティングソートに似ていますが、数値が「バケット」範囲に収集され、その後、他の十分に生産性の高いソートアルゴリズムを使用してバケットがソートされ、最後のステップで「バケット」の範囲が展開される点が異なります。 1 ずつ増やすと、ソートされたリストが得られます。

    アルゴリズムの時間計算量は O(nk) です。このアルゴリズムは、一様分布の法則に従うデータに対して線形時間で機能します。簡単に言うと、要素は「スパイク」のない特定の範囲内にある必要があります (たとえば、0.0 から 1.0 までの数値)。そのような数字の中に 4 または 999 がある場合、中庭法によれば、そのような列は「偶数」とみなされなくなります。

    Julia での実装例:

        buckets = Vector{Vector{Int}}()
        
        for i in 0:bucketsCount - 1
            bucket = Vector{Int}()
            push!(buckets, bucket)
        end
    
        maxNumber = maximum(numbers)
    
        for i in 0:length(numbers) - 1
            bucketIndex = 1 + Int(floor(bucketsCount * numbers[1 + i] / (maxNumber + 1)))
            push!(buckets[bucketIndex], numbers[1 + i])
        end
    
        for i in 0:length(buckets) - 1
            bucketIndex = 1 + i
            buckets[bucketIndex] = sort(buckets[bucketIndex])
        end
    
        flat = [(buckets...)...]
        print(flat, "\n")
    
    end
    
    numbersCount = 10
    maxNumber = 10
    numbers = rand(1:maxNumber, numbersCount)
    print(numbers,"\n")
    bucketsCount = 10
    bucketSort(numbers, bucketsCount)

    На производительность алгоритма также влияет число ведер, для большего количества чисел лучше взять большее число ведер (Algorithms in a nutshell by George T. Heineman)

    Ссылки

    https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/bucketSort

    Источники

    https://www.youtube.com/watch?v=VuXbEb5ywrU
    https://www.youtube.com/watch?v=ELrhrrCjDOA
    https://medium.com/karuna-sehgal/an-introduction-to-bucket-sort-62aa5325d124
    https://www.geeksforgeeks.org/bucket-sort-2/
    https://ru.wikipedia.org/wiki/%D0%91%D0%BB%D0%BE%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0
    https://www.youtube.com/watch?v=LPrF9yEKTks
    https://en.wikipedia.org/wiki/Bucket_sort
    https://julialang.org/
    https://www.oreilly.com/library/view/algorithms-in-a/9780596516246/ch04s08.html

    基数ソート

    基数ソート – 基数ソート。このアルゴリズムは、要素の比較がないという点でカウント ソートに似ています。代わりに、要素が *文字ごと* に *バケット* (バケット) にグループ化され、バケットは現在の数値文字のインデックスによって選択されます。時間計算量 – O(nd)。

    次のように動作します:

    • 入力は数字 6、12、44、9 になります
    • リスト (0 ~ 9) のバケットを 10 個作成し、そこに少しずつ数字を追加/並べ替えます。

    次へ:

    <オル>

  • 数値内の最大文字数までのカウンター i でループを開始します
  • インデックス i を右から左に指定すると、各数値に対して 1 つの記号が得られ、記号がない場合は 0 であると見なされます。
  • 記号を数値に変換する
  • インデックス番号でバケットを選択し、そこに整数を入力します
  • 数値の検索が終了したら、すべてのバケットを数値のリストに変換し直します
  • ランク順に並べ替えられた数値を取得する
  • すべての数字がなくなるまで繰り返します
  • Scala での基数ソートの例:

    
    import scala.util.Random.nextInt
    
    
    
    object RadixSort {
    
        def main(args: Array[String]) = {
    
            var maxNumber = 200
    
            var numbersCount = 30
    
            var maxLength = maxNumber.toString.length() - 1
    
    
    
            var referenceNumbers = LazyList.continually(nextInt(maxNumber + 1)).take(numbersCount).toList
    
            var numbers = referenceNumbers
    
            
    
            var buckets = List.fill(10)(ListBuffer[Int]())
    
    
    
            for( i <- 0 to maxLength) { numbers.foreach( number => {
    
                        var numberString = number.toString
    
                        if (numberString.length() > i) {
    
                            var index = numberString.length() - i - 1
    
                            var character = numberString.charAt(index).toString
    
                            var characterInteger = character.toInt  
    
                            buckets.apply(characterInteger) += number
    
                        }
    
                        else {
    
                            buckets.apply(0) += number
    
                        }
    
                    }
    
                )
    
                numbers = buckets.flatten
    
                buckets.foreach(x => x.clear())
    
            }
    
            println(referenceNumbers)
    
            println(numbers)
    
            println(s"Validation result: ${numbers == referenceNumbers.sorted}")
    
        }
    
    }
    
    

    このアルゴリズムには、GPU などで並列実行するためのバージョンもあります。ちょっとした並べ替えオプションもあります。これは非常に興味深く、本当に息をのむようなものに違いありません。

    リンク

    https://gitlab .com/demensdeum/algorithms/-/blob/master/sortAlgorithms/radixSort/radixSort.scala

    ソース

    https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D 0%B4%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0% BA%D0%B0
    https://www.geeksforgeeks.org/radix-sort/

    https://www.youtube.com/watch?v=toAlAJKojos

    https://github.com/gyatskov/radix-sort

    ヒープソート

    ヒープソート – ピラミッドソート。アルゴリズムの時間計算量 – O(n log n)、速いですよね?私はこれを、落ちてくる小石の選別と呼んでいます。これを説明する最も簡単な方法は視覚的に説明することだと思います。

    入力は、次のような数値のリストです。
    5、0、7、2、3、9、4

    左から右に、データ構造、つまりバイナリ ツリー、または私がそれを呼んでいる – が作成されます。ピラミッド。ピラミッド要素には最大 2 つの子要素を含めることができますが、最上位の要素は 1 つだけです。

    バイナリ ツリーを作成しましょう:
    ⠀⠀5
    ⠀0⠀7
    2 3 9 4

    ピラミッドを長い間見てみると、これらは配列からの単なる数値であり、次々に来て、各フロアの要素の数が 2 倍になっていることがわかります。

    次に、楽しい作業が始まります。小石を落とす方法 (heapify) を使用して、ピラミッドを下から上に並べ替えましょう。最終階(2 3 9 4)から並べ替えを開始することもできますが、意味がありません。以下に転落する可能性のある床はありません。

    したがって、最後から 2 番目のフロア (0 7) から要素をドロップし始めます。
    ⠀⠀5
    ⠀0⠀7
    2 3 9 4

    最初に該当する要素が右から選択されます。この場合は 7 です。次に、その下にあるものを調べます。その下には 9 と 4 があり、9 は 4 より大きく、9 はより大きいです。セブン! 7 を 9 に落とし、9 を持ち上げて 7 の位置に置きます。
    ⠀⠀5
    ⠀0⠀9
    2 3 7 4

    次に、7 には下に落ちるところがないことがわかり、左側の最後から 2 番目の階にある番号 0 に進みます。
    ⠀⠀5
    0⠀9
    2 3 7 4

    その下にあるものを見てみましょう – 2 と 3、2 は 3 より小さく、3 は 0 より大きいので、0 と 3 を交換します。
    ⠀⠀5
    ⠀3⠀9
    2 0 7 4

    フロアの端に到達したら、上のフロアに行き、可能であればそこにすべてを落としてください。
    結果はデー​​タ構造、つまりヒープ、つまり最大ヒープになります。一番上にあるのは最大の要素です。
    ⠀⠀9
    ⠀3⠀7
    2 0 5 4

    これを配列表現に戻すと、リストが得られます。
    [9、3、7、2、0、5、4]

    このことから、最初と最後の要素を交換することで、最終的な並べ替え位置の最初の数値が得られる、つまり 9 が並べ替えられたリストの最後にあるはずであると結論付けることができ、場所を入れ替えます。
    [4、3、7、2、0、5、9]

    二分木を見てみましょう:
    ⠀⠀4
    ⠀3⠀7
    2 0 5 9

    結果は、ツリーの下の部分がソートされた状況になります。必要なのは、4 を正しい位置にドロップし、アルゴリズムを繰り返すだけです。ただし、すでにソートされている数値、つまり 9 は考慮しません。
    ⠀⠀4
    ⠀3⠀7
    2 0 5 9

    ⠀⠀7
    ⠀3⠀4
    2 0 5 9

    ⠀⠀7
    ⠀3⠀5
    2 0 4 9

    4 を落とした後、9 – の次に大きな数字を上げたことが判明しました。 7. ソートされていない最後の数字 (4) と最大の数字 (7) を交換します
    ⠀⠀4
    ⠀3⠀5
    2 0 7 9

    正しい最終位置に 2 つの数値があることがわかりました。
    4、3、5、2、0、79

    次に、既に並べ替えられたアルゴリズムを無視して並べ替えアルゴリズムを繰り返します。最終的には ヒープ タイプ:
    ⠀⠀0
    ⠀2⠀3
    4 5 7 9

    またはリストとして:
    0、2、3、4、5、7、9

    実装

    アルゴリズムは通常、次の 3 つの機能に分割されます。

    <オル>

  • ヒープの作成
  • ふるい分けアルゴリズム (heapify)
  • ソートされていない最後の要素と最初の要素を置き換えます
  • ヒープは、heapify 関数を使用してバイナリ ツリーの最後から 2 番目の行を右から左に配列の末尾まで走査することによって作成されます。次にサイクルでは、最初の数値の置換が行われ、その後、最初の要素が配置されるか、その位置に残ります。その結果、最大の要素が 1 位に配置され、参加者が 1 人減ってサイクルが繰り返されます。各パスの後、ソートされた数値がリストの最後に残ります。

    Ruby でのヒープソートの例:

    
    
    
    
    
    module Colors
    
    
    
        BLUE = "\033[94m"
    
    
    
        RED = "\033[31m"
    
    
    
        STOP = "\033[0m"
    
    
    
    end
    
    
    
    
    
    
    
    def heapsort(rawNumbers)
    
    
    
        numbers = rawNumbers.dup
    
    
    
    
    
    
    
        def swap(numbers, from, to)
    
    
    
            temp = numbers[from]
    
    
    
            numbers[from] = numbers[to]
    
    
    
            numbers[to] = temp
    
    
    
        end
    
    
    
    
    
    
    
        def heapify(numbers)
    
    
    
            count = numbers.length()
    
    
    
            lastParentNode = (count - 2) / 2
    
    
    
    
    
    
    
            for start in lastParentNode.downto(0)
    
    
    
                siftDown(numbers, start, count - 1)
    
    
    
                start -= 1 
    
    
    
            end
    
    
    
    
    
    
    
            if DEMO
    
    
    
                puts "--- heapify ends ---"
    
    
    
            end
    
    
    
        end
    
    
    
    
    
    
    
        def siftDown(numbers, start, rightBound)      
    
    
    
            cursor = start
    
    
    
            printBinaryHeap(numbers, cursor, rightBound)
    
    
    
    
    
    
    
            def calculateLhsChildIndex(cursor)
    
    
    
                return cursor * 2 + 1
    
    
    
            end
    
    
    
    
    
    
    
            def calculateRhsChildIndex(cursor)
    
    
    
                return cursor * 2 + 2
    
    
    
            end            
    
    
    
    
    
    
    
            while calculateLhsChildIndex(cursor) <= rightBound
    
    
    
                lhsChildIndex = calculateLhsChildIndex(cursor)
    
    
    
                rhsChildIndex = calculateRhsChildIndex(cursor)
    
    
    
    
    
    
    
                lhsNumber = numbers[lhsChildIndex]
    
    
    
                biggerChildIndex = lhsChildIndex
    
    
    
    
    
    
    
                if rhsChildIndex <= rightBound
    
    
    
                    rhsNumber = numbers[rhsChildIndex]
    
    
    
                    if lhsNumber < rhsNumber
    
    
    
                        biggerChildIndex = rhsChildIndex
    
    
    
                    end
    
    
    
                end
    
    
    
    
    
    
    
                if numbers[cursor] < numbers[biggerChildIndex]
    
    
    
                    swap(numbers, cursor, biggerChildIndex)
    
    
    
                    cursor = biggerChildIndex
    
    
    
                else
    
    
    
                    break
    
    
    
                end
    
    
    
                printBinaryHeap(numbers, cursor, rightBound)
    
    
    
            end
    
    
    
            printBinaryHeap(numbers, cursor, rightBound)
    
    
    
        end
    
    
    
    
    
    
    
        def printBinaryHeap(numbers, nodeIndex = -1, rightBound = -1)
    
    
    
            if DEMO == false
    
    
    
                return
    
    
    
            end
    
    
    
            perLineWidth = (numbers.length() * 4).to_i
    
    
    
            linesCount = Math.log2(numbers.length()).ceil()
    
    
    
            xPrinterCount = 1
    
    
    
            cursor = 0
    
    
    
            spacing = 3
    
    
    
            for y in (0..linesCount)
    
    
    
                line = perLineWidth.times.map { " " }
    
    
    
                spacing = spacing == 3 ? 4 : 3
    
    
    
                printIndex = (perLineWidth / 2) - (spacing * xPrinterCount) / 2
    
    
    
                for x in (0..xPrinterCount - 1)
    
    
    
                    if cursor >= numbers.length
    
    
    
                        break
    
    
    
                    end
    
    
    
                    if nodeIndex != -1 && cursor == nodeIndex
    
    
    
                        line[printIndex] = "%s%s%s" % [Colors::RED, numbers[cursor].to_s, Colors::STOP]
    
    
    
                    elsif rightBound != -1 && cursor > rightBound
    
    
    
                        line[printIndex] = "%s%s%s" % [Colors::BLUE, numbers[cursor].to_s, Colors::STOP]
    
    
    
                    else
    
    
    
                        line[printIndex] = numbers[cursor].to_s
    
    
    
                    end
    
    
    
                    cursor += 1
    
    
    
                    printIndex += spacing
    
    
    
                end
    
    
    
                print line.join()
    
    
    
                xPrinterCount *= 2           
    
    
    
                print "\n"            
    
    
    
            end
    
    
    
        end
    
    
    
    
    
    
    
        heapify(numbers)
    
    
    
        rightBound = numbers.length() - 1
    
    
    
    
    
    
    
        while rightBound > 0
    
    
    
            swap(numbers, 0, rightBound)   
    
    
    
            rightBound -= 1
    
    
    
            siftDown(numbers, 0, rightBound)     
    
    
    
        end
    
    
    
    
    
    
    
        return numbers
    
    
    
    end
    
    
    
    
    
    
    
    numbersCount = 14
    
    
    
    maximalNumber = 10
    
    
    
    numbers = numbersCount.times.map { Random.rand(maximalNumber) }
    
    
    
    print numbers
    
    
    
    print "\n---\n"
    
    
    
    
    
    
    
    start = Time.now
    
    
    
    sortedNumbers = heapsort(numbers)
    
    
    
    finish = Time.now
    
    
    
    heapSortTime = start - finish
    
    
    
    
    
    
    
    start = Time.now
    
    
    
    referenceSortedNumbers = numbers.sort()
    
    
    
    finish = Time.now
    
    
    
    referenceSortTime = start - finish
    
    
    
    
    
    
    
    print "Reference sort: "
    
    
    
    print referenceSortedNumbers
    
    
    
    print "\n"
    
    
    
    print "Reference sort time: %f\n" % referenceSortTime
    
    
    
    print "Heap sort:      "
    
    
    
    print sortedNumbers
    
    
    
    print "\n"
    
    
    
    if DEMO == false
    
    
    
        print "Heap sort time:      %f\n" % heapSortTime
    
    
    
    else
    
    
    
        print "Disable DEMO for performance measure\n"
    
    
    
    end
    
    
    
    
    
    
    
    if sortedNumbers != referenceSortedNumbers
    
    
    
        puts "Validation failed"
    
    
    
        exit 1
    
    
    
    else
    
    
    
        puts "Validation success"
    
    
    
        exit 0
    
    
    
    end
    
    
    
    

    このアルゴリズムは視覚化しないと理解しにくいため、最初にバイナリ ツリーの現在のビューを出力する関数を作成することをお勧めします。

    リンク

    https://gitlab.com/demensdeum/algorithms/-/blob/master/sortAlgorithms/heapsort/heapsort.rb

    ソース

    http://rosettacode.org/wiki/Sorting_algorithms/Heapsort
    https://www.youtube.com/watch?v=LbB357_RwlY

    https://habr.com/ru/company/ otus/blog/460087/

    https://ru.wikipedia.org/wiki/Pyramid_sort

    https://neerc.ifmo.ru/wiki /index.php?title=ヒープソート

    https://wiki5.ru/wiki/Heapsort

    https://wiki.c2.com/?HeapSort

    https://ru.wikipedia.org/wiki/Tree (データ構造)

    https://ru.wikipedia.org/wiki/Heap (データ構造)

    https://www.youtube.com/watch?v=2DmK_H7IdTo

    https://www.youtube.com/watch?v=kU4KBD4NFtw

    https://www.youtube.com/watch?v=DU1uG5310x0

    https://www.youtube.com/watch?v =BzQGPA_v-vc

    https://www.geeksforgeeks.org/バイナリヒープの配列表現/

    https://habr.com/ru/post/112222/

    https://www.cs.usfca. edu/~galles/visualization/BST.html

    https://www.youtube.com/watch?v=EQzqHWtsKq4

    https://medium.com/@dimko1/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC% D1 %8B-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-heapsort-796ba965018b

    https://ru.wikibrief.org/wiki/Heapsort

    https://www.youtube.com/watch?v=GUUpmrTnNbw

    Bumblebee All Troubles

    Recently, it turned out that users of modern Nvidia GPUs under Arch Linux do not need to use the bumblebee package at all, for example, for me it did not detect an external monitor when connected. I recommend removing the bumblebee package and all related packages, and installing prime using the instructions on the Arch Wiki.
    Next, to launch all games on Steam and 3D applications, add prime-run, for Steam this is done like this prime-run %command% in additional launch options.
    To check the correctness, you can use glxgears, prime-run glxgears.
    https://bbs.archlinux.org/viewtopic.php? pid=2048195#p2048195