Windows 11 対応について
システム要件はこちらに書かれてある通り。
- CPU:1GHz以上2コア以上の64ビット
- メモリ:4GB以上
- ストレージ:64GB以上
- グラフィックボード:DirectX12
- ディスプレイ:9インチ以上,720p以上
- Microsoftのアカウントとインターネット接続が必要
- ファームウェア:UEFI、セキュアブート対応
- TPM:2.0以上
この中で、問題となりそうなのは ファームウェアと、TPM。
TPMの対応は、[Windows]キー > "tpm.msc + [Enter]"
と打ち込むとウィンドウが表示されるので、そこで確認できる。
しかし、現在対応していない場合でもBIOSの設定にて対応できる可能性がある。
TPMについて
- 参考
- TPM(Trusted Platform Module)はHDDを暗号化するなど、セキュリティ機能を提供するためのモジュール
- マザーボード上の物理的なチップとして提供される。マザーボード上の専用端子にモジュールとして追加できるタイプもある。
- また、CPUのソフトウェアモジュールとして機能が搭載されている。
CPUのTPMは無効化されている可能性が高いのでBIOSでONにする。以下参照。
https://pssection9.com/archives/windows11-support-cpu.html#st-toc-h-18ちなみに自分のPCで確認した所 CPUはi7-6700K、チップセット intel H110 で両方とも intel PTT対応になっているが、BIOSの設定項目が無くONにできなかった。 どうやら CPU側に Intel TXT(Trusted Execution Technology)が無い為、BIOSの信頼性が保てないのでBIOS設定から除外されている模様。 関連記事
高速なファイル検索
WindowsのツールにてWizTreeというストレージの使用容量を表示するアプリケーションがある。 https://forest.watch.impress.co.jp/library/software/wiztree/
このツールは、NTFSのMFT(マスターファイルテーブル)という領域を使用して高速にファイルサイズを取得する仕組みになっている。
MFTについて
Windowsのローカルファイルシステムについて
MFTを使えば、高速なファイル検索も可能と思われる。
C++でアクセスする方法
C#でアクセスする方法
p4cmd コマンドの作成
p4cmd は p4 コマンドにアクセス用のグローバルオプションを付加して、コマンド実行する仕組みです。
例えば
p4cmd changelists -m 10 -l
というコマンドは、以下のようにグローバルオプションが追加されて実行されます。
p4 -p {Server:Port} -u {UserName} -c {ClientName} changelists -m 10 -l
グルーバルオプションにて指定される {Server:Port} {UserName} {ClientName} は
実行するカレントディレクトリもしくはコマンド引数内にあるパス名から自動的に判別されます。
p4cmd は使用する前に Perforceサーバーへのアクセス設定をする必要があります。 アクセス設定を行うとクライアント設定にてマッピングされたワークスペースの ディレクトリ情報を保持してコマンド実行時にその範囲に入っていれば、 アクセス設定をグローバルオプションとして追加します。
ワークスペースに含まれるかどうかは次の手順で判定されます。
Perforce へのアクセス
Perforce へのアクセスには、4つの情報が必要になる。
アクセスに必要な情報は、 p4 set {環境変数名}={値}
命令にてPerforce用の環境変数(Windowsのみ)として設定することができる。
p4 コマンドを使用する際には環境変数が使用されるが、グローバルオプションにて設定を上書きすることができる。
指定 | 環境変数名 | グローバルオプション |
---|---|---|
サーバー名(+ポート番号) | P4PORT | -p |
ユーザー名 | P4USER | -u |
クライアント名 | P4CLIENT | -c |
パスワード | P4PASSWD | -P |
複数の depot を扱う環境では、扱うディレクトリによってはこれを切り替える必要が出てくる。 p4 コマンドを使用する際に、PCの環境、ユーザーごとにこれらの設定が違うため、自動化のバッチが作りにくい理由となる。
環境変数
環境変数とあるが、Windiws では OSの環境変数とは違うため、プロセスによる切り替えができない。
※ MacOS や Unix ではOS環境変数なのに・・・・。
コマンドプロンプトを2つ立ち上げて片方の設定を変えると、もう片方にも反映してしまう。
つまりOSでグローバルな設定となっている。
これは p4 コマンドで自動化を処理をする際に非常に大きな問題となる。
複数の depot を扱っているPCで同時に別々のp4コマンドを使用する場合、
P4環境変数は共有されているため、タイミングによってどちらの設定が反映するかわからないので使用できない。
Windows では 必ずグローバルオプションで指定する必要がある。
パスワード入力について
パスワードに関してはグローバルオプションや環境変数に直に記入するとセキュリティの問題がある為、 サーバーのセキュリティレベルによってはグローバルオプション、環境変数の指定が無効にされる。 その場合、チケットによる認証が行われる。ログイン時にチケットがサーバーから発行されチケットの有効期間はパスワードを入力しなくても良くなる。
当然ログイン時にパスワードが必要になるが、グローバルオプションと環境変数は無効とされているため、標準入力から入力する必要がある。 セキュリティ的に問題はあるが、バッチで処理する場合は以下のようにするとログインを自動化できる。
echo {Password} | p4 login -p {Server:Port} -u {UserName} -c {ClientName}
解決策
上記のことを踏まえると、Perforce の処理を自動化するには、depotがマッピングされたディレクトリによってアクセスへの設定を切り替えられるのが良い。 例えば、ローカルPC毎にPerforeceへのログイン設定を行ってもらい、使用するディレクトリによって 自動的にグローバルオプションを付加したコマンドを発行する仕組みがあれば良い。
C# クラスオブジェクトの比較
クラスを比較する場合について、詳しくは以下のサイトに書かれている。 自作クラスのEqualsメソッドをオーバーライドして、等価の定義を変更する - .NET Tips (VB.NET,C#...)
・デフォルトの Equals()実装では参照型(class)はclassの参照先が同じかどうか、値型(struct)はメンバの内容が同じかどうか、を比較している。 ・値型(struct)のメンバの比較の際にも同様に判定している。
クラスの中身を比較する場合、2通りの方法がある。
1)object.Equals() の仮想関数をオーバーライド
2)==, != の比較演算子をオーバーロード
VS2017 では1)2)を自動生成をしてくれる。
Generate C# Equals and GetHashCode Method Overrides - Visual Studio | Microsoft Docs
次のクラスを
public class Hoge { int id; string name; }
以下のようなコードを作成してくれる。(※整形&コメント追加)
public class Hoge : IEquatable<Hoge> // IEquatableインターフェイスを追加 { int id; string name; // 仮想関数 Equals() をオーバーライド public override bool Equals(object obj) { return Equals(obj as Hoge); } // Equals() の実態。オーバーライドと分けることで、型がわかっている場合に as 演算子を呼び出さなくて済む。 public bool Equals(Hoge other) { return other != null && id == other.id && name == other.name ; } // ハッシュコード生成。 public override int GetHashCode() { var hashCode = -48284730; hashCode = hashCode * -1521134295 + id.GetHashCode(); hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(name); return hashCode; } // == 演算子オーバーロード。Equals() を呼び出す。 public static bool operator ==(Hoge hoge1, Hoge hoge2) { return EqualityComparer<Hoge>.Default.Equals(hoge1, hoge2); } // != 演算子オーバーロード。== 演算子を呼び出す。 public static bool operator !=(Hoge hoge1, Hoge hoge2) { return !(hoge1 == hoge2); } }
UE4:オブジェクトの扱い
生成と破棄について
UE4はガベージコレクション(以下GC)を実装している為、普通のC++とは違う方法が必要になってくる。
まず、GC対象になるにはUObject
を継承する必要がある。正確には更に基底クラスがあるが(UObjectBase
> UObjectBaseUtility
> UObject
の継承)、プログラマが基本的に使うのは、UObject
になる。
生成
生成は NewObject
を使用する。幾つかの指定方法がある。
Unreal Engine | UObject インスタンスの作成
UTestObj* TestObj = NewObject<UTestObj>();
引数に Outer があるが、Outer が何を指すべきかが分かりにくい。UE4 Outerについて調査してみた - ハッカーと同人作家 にもあるが、Owner とは別の扱いになる。
破棄
破棄については調べてみても意外と書かれていない。C++でGCを実装するには、通常「共有ポインタで参照が情報を保持していて、参照が無くなった時点で自動的に削除される」といった手法を取るが、UE4ではスマートポインタでは無い「普通のC++のポインタ」も結構使われている。
「C++のポインタ」を nullptr にした場合には自動的に消えるものだろうか?C++言語で記述されているので「C++のポインタ」を書き換えたという事を普通では認識できないはずだが。
Unreal Engine | アンリアルでのオブジェクト処理 を見ると、
ガーベジ コレクションが実施されると、既知の UObject 参照のツリーを "root set" から開始して検索することで、参照されているすべての UObjects を追跡することができます。参照されていない UObjects、つまりツリー検索で見つからなかったものは、不要とみなされ、取り除かれます。
UObject 継承は"root set"ツリーで管理されていて、ツリーから外れると削除対象になる。これだけではツリーから外れるルールが分らない。
AActor または UActorComponent が破棄されるか、プレイから取り除かれると、それに対するすべての参照でリフレクション システムで可視のものは (UProperty ポインタおよび TArray など Unreal Engine コンテナ クラスに保存されているポインタ) 自動的に null になります。
とある。これはオブジェクトを破棄をすると、オブジェクトを参照しているポインタを nullptr にすると書かれている。「nullptr にしたら消える」では無い模様。あと「可視」のものが自動的に nullptr なるとの事がだ、「不可視」のものはされないという事か?この「可視・不可視」何を指すのだろう。
通常はガーベジ コレクションの対象から外したいオブジェクトに対する UPROPERTY 参照を保持しておく必要があります。または、それに対するポインタを TArray またはアンリアルエンジンの他のコンテナ クラスに格納します。多くの場合、アクタはこの例外です。アクタは通常、ルートセットにリンクバックするオブジェクトによって参照されるからです。例えば、それらが属するレベルなどで、アクタのコンポーネントはアクタ自体によって参照されます。アクタは、Destroy 関数を呼び出すことで明示的に破棄とマーク付けされます。これは進行中のゲームからアクタを取り除く標準的な方法です。コンポーネントは、DestroyComponent 関数を使って明示的に破棄できますが、通常は所有しているアクタがゲームから取り除かれると破棄されます。
イマイチ分かりにくいが、まとめると
- UPROPERTY か、UE4のコンテナクラスで参照しているものは、GC対象にならない。
- アクタ、コンポーネントはそれぞれレベル、アクタに参照されるので、GC対象にならない。
- アクタとコンポーネントは次の関数で明示的に破棄できる。
MyActor->Destory(); MyActorComponent->DestoryComponent();
ポインタの参照の調査
これだけだと、あまり細かく事まで分からないので色々と調べてみることにした。
調べる内容
- CPPObj, UObject, UActorComponent, AActor のクラスを生成し、以下の種類のポインタで保持する。それぞれ、GCがどの様に働くのかを調査。
A) C++ポインタ
B) UPROPERTY指定のC++ポインタ
C) スマートポインタ(TWeakPointer)
D) コンテナクラス(TArray)での挙動を調査 - ポインタを何も操作していな場合、GCで破棄されるか?
- ポインタを nullptr に書き換えた場合、GCで破棄されるか?
- 明示的に破棄関数を呼び出した場合、GCで破棄されるか?
- 破棄された場合に、それを指していたポインタに nullptr が入るかどうか?
結果
何も操作していない | ポインタをnullptrに書き換えた場合 | 破棄関数を呼び出した | ||
---|---|---|---|---|
C++オブジェクト | C++ポインタ | 即座に破棄 | ||
C++オブジェクト | コンテナクラス | 即座に破棄 | ||
UObject | C++ポインタ | GCで遅延破棄 | GCで遅延破棄 | |
UObject | UPROPERTY指定のC++ポインタ | GCで遅延破棄 | GCでオーナーの AtestActor 破棄される直前に破棄 | |
UObject | スマートポインタ | GCで遅延破棄GC破棄後にnullptrに書き換え | GCで遅延破棄GC破棄後にnullptrに書き換え | |
UObject | コンテナクラス | GCで遅延破棄 | GCで遅延破棄 | |
UActorComponent | C++ポインタ | GCで遅延破棄 | ||
UActorComponent | UPROPERTY指定のC++ポインタ | GCで遅延破棄GC破棄後にnullptrに書き換え | ||
UActorComponent | スマートポインタ | GCで遅延破棄 nullptrに書き換え | ||
UActorComponent | コンテナクラス | GCで遅延破棄 | ||
AActor | C++ポインタ | GCで遅延破棄 | GCで遅延破棄 | |
AActor | UPROPERTY指定のC++ポインタ | GCで遅延破棄 | GCで遅延破棄GC破棄後にnullptrに書き換え | |
AActor | スマートポインタ | GCで遅延破棄GC破棄後にnullptrに書き換え | GCで遅延破棄 nullptrに書き換え | |
AActor | コンテナクラス | GCで遅延破棄 | GCで遅延破棄 |
何も操作していない場合
- UObject_A/C/D と Actor_A/C/D が放置しておくと破棄された。つまり、GCで依存関係が調査されて、切れていると判断された、という事。
- UPROPERTY指定やスマートポインタで保持しておくだけでは、リンクでつながっている事とは言えないという事になる。
- 自動的に破棄されると、スマートポインタ内はクリアされる。これはドキュメントの説明にあった通り。
- UComponent_A/B/C/D は「ATestDestroy が破棄される時に」破棄される。ATestDestroy 依存関係があると判断されてる。
- CPPObj_A/Dは破棄されない。GCからは監視されていない。
ポインタを nullptr に書き換えた場合
- UObject_A/B/C/D、Actor_A/B/C/D が時間が経って破棄された。上記と比較すると UObject_B、Actor_B が破棄されている。 つまり、UPROPERTY指定ポインタはGCで依存関係を監視していて、nullptr が書き込まれたことで依存が無くなったと判断されたことになる。
- UComponent_A/B/C/D は上と同じ。
破棄関数を呼び出した場合。
- 当然、全てのオブジェクトは破棄される。CPPObj_A/Dは即時、それ以外はGCにより時間が経って破棄された。
- ポインタの状態に関しては、スマートポインタのみ破棄される。
未調査
- スマートポインタはTWeakObjectPtr<>なので、TSharedObjectPtr<>とでは結果が違いそう。
- AActor は、通常生成時に使う Spawn を使用しておらず、その為アウトライナ上にはいない。Spawn での生成も調査が必要。
コード
#pragma once #include "CoreMinimal.h" #include "Components/SceneComponent.h" #include "GameFramework/Actor.h" #include "Kismet/GameplayStatics.h" #include "TestDestroy.generated.h" class FDestroyedCPPObj { private: FString SaveName; public: FDestroyedCPPObj(const FString& Name) : SaveName(Name) {} ~FDestroyedCPPObj() { UE_LOG(LogTemp, Log, TEXT("FDestroyedCPPObj::~FDestroyedCPPObj (%s)"), *SaveName); } }; UCLASS() class UDestroyedObject : public UObject { GENERATED_BODY() private: FString SaveName; public: UDestroyedObject() { SaveName = GetName(); } virtual ~UDestroyedObject() { UE_LOG(LogTemp, Log, TEXT("UDestroyedObject::~UDestroyedObject (%s)"), *SaveName); } }; UCLASS() class UDestroyedComponent : public UActorComponent { GENERATED_BODY() private: FString SaveName; public: UDestroyedComponent() { SaveName = GetName(); } virtual ~UDestroyedComponent() { UE_LOG(LogTemp, Log, TEXT("UDestroyedComponent::~UDestroyedComponent (%s)"), *SaveName); } }; UCLASS() class ADestroyedActor : public AActor { GENERATED_BODY() private: FString SaveName; public: ADestroyedActor() { SaveName = GetName(); } virtual ~ADestroyedActor() { UE_LOG(LogTemp, Log, TEXT("ADestroyedActor::~ADestroyedActor (%s)"), *SaveName); } }; UCLASS() class ATestDestory : public AActor { GENERATED_BODY() private: FDestroyedCPPObj* CPPObj_A; // ポインタ TArray<FDestroyedCPPObj*> CPPObj_D; // ポインタの配列 UDestroyedObject* UObject_A; // ポインタ UPROPERTY() UDestroyedObject* UObject_B; // UPROPERTYありポインタc TWeakObjectPtr<UDestroyedObject> UObject_C; // Weakスマートポインタ TArray<UDestroyedObject*> UObject_D; // ポインタの配列 UDestroyedComponent* Component_A; // ポインタ UPROPERTY() UDestroyedComponent* Component_B; // UPROPERTYありポインタ TWeakObjectPtr<UDestroyedComponent> Component_C; // Weakスマートポインタ TArray<UDestroyedComponent*> Component_D; // ポインタの配列 ADestroyedActor* Actor_A; // ポインタ UPROPERTY() ADestroyedActor* Actor_B; // UPROPERTYありポインタ TWeakObjectPtr<ADestroyedActor> Actor_C; // Weakポインタ TArray<ADestroyedActor*> Actor_D; // TArrayポインタ uint32 KeyBitOld = 0; float CountSeconds = 0.0f; private: void OutputPointer() { UE_LOG(LogTemp, Log, TEXT("CPPObj :A/-/-/D(%p/----------------/----------------/%p)") ,CPPObj_A ,CPPObj_D[0]); UE_LOG(LogTemp, Log, TEXT("UObject :A/B/C/D(%p/%p/%p/%p)") ,UObject_A ,UObject_B ,UObject_C.Get() ,UObject_D[0]); UE_LOG(LogTemp, Log, TEXT("Component:A/B/C/D(%p/%p/%p/%p)") ,Component_A ,Component_B ,Component_C.Get() ,Component_D[0]); UE_LOG(LogTemp, Log, TEXT("Actor :A/B/C/D(%p/%p/%p/%p)") ,Actor_A ,Actor_B ,Actor_C.Get() ,Actor_D[0]); } public: ATestDestory() { PrimaryActorTick.bCanEverTick = true; CPPObj_D .Add(nullptr); UObject_D .Add(nullptr); Component_D.Add(nullptr); Actor_D .Add(nullptr); } virtual ~ATestDestory() { UE_LOG(LogTemp, Log, TEXT("ATestDestory::~ATestDestory()")); OutputPointer(); } virtual void BeginPlay() override { Super::BeginPlay(); CPPObj_A = new FDestroyedCPPObj(FString(TEXT("CPPObj_A"))); CPPObj_D[0] = new FDestroyedCPPObj(FString(TEXT("CPPObj_D"))); UObject_A = NewObject<UDestroyedObject>(this, "UObject_A"); UObject_B = NewObject<UDestroyedObject>(this, "UObject_B"); UObject_C = NewObject<UDestroyedObject>(this, "UObject_C"); UObject_D[0] = NewObject<UDestroyedObject>(this, "UObject_D"); Component_A = NewObject<UDestroyedComponent>(this, TEXT("Component_A")); Component_B = NewObject<UDestroyedComponent>(this, TEXT("Component_B")); Component_C = NewObject<UDestroyedComponent>(this, TEXT("Component_C")); Component_D[0] = NewObject<UDestroyedComponent>(this, TEXT("Component_D")); Actor_A = NewObject<ADestroyedActor>(this, TEXT("Actor_A")); Actor_B = NewObject<ADestroyedActor>(this, TEXT("Actor_B")); Actor_C = NewObject<ADestroyedActor>(this, TEXT("Actor_C")); Actor_D[0] = NewObject<ADestroyedActor>(this, TEXT("Actor_D")); OutputPointer(); UE_LOG(LogTemp, Log, TEXT("ATestDestory::BeginPlay()")); } virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override { UE_LOG(LogTemp, Log, TEXT("ATestDestory::EndPlay()")); OutputPointer(); Super::EndPlay(EndPlayReason); } virtual void Tick( float DeltaSeconds ) override { Super::Tick(DeltaSeconds); CountSeconds += DeltaSeconds; if( CountSeconds > 1.0f) { CountSeconds -= 1.0f; OutputPointer(); } auto Controller0 = UGameplayStatics::GetPlayerController(this, 0); if (Controller0 == nullptr) return; uint32 KeyBit = (Controller0->IsInputKeyDown(EKeys::NumPadZero ) ? 1<<0 : 0) | (Controller0->IsInputKeyDown(EKeys::NumPadOne ) ? 1<<1 : 0) | (Controller0->IsInputKeyDown(EKeys::NumPadTwo ) ? 1<<2 : 0) | (Controller0->IsInputKeyDown(EKeys::NumPadThree) ? 1<<3 : 0) | (Controller0->IsInputKeyDown(EKeys::NumPadFour ) ? 1<<4 : 0) | (Controller0->IsInputKeyDown(EKeys::NumPadFive ) ? 1<<5 : 0) | (Controller0->IsInputKeyDown(EKeys::NumPadSix ) ? 1<<6 : 0) | (Controller0->IsInputKeyDown(EKeys::NumPadSeven) ? 1<<7 : 0) | (Controller0->IsInputKeyDown(EKeys::NumPadEight) ? 1<<8 : 0) | (Controller0->IsInputKeyDown(EKeys::NumPadNine ) ? 1<<9 : 0) ; uint32 KeyPress = KeyBit & ~KeyBitOld; KeyBitOld = KeyBit; if(KeyPress&(1<<0)) { Destroy(); } if(KeyPress&(1<<1)) { UE_LOG(LogTemp, Log, TEXT("-------------------------------- CPPObj : nullptr")); CPPObj_A = nullptr; CPPObj_D[0] = nullptr; OutputPointer(); } if(KeyPress&(1<<2)) { UE_LOG(LogTemp, Log, TEXT("-------------------------------- UObject : nullptr")); UObject_A = nullptr; UObject_B = nullptr; UObject_C = nullptr; UObject_D[0] = nullptr; OutputPointer(); } if(KeyPress&(1<<3)) { UE_LOG(LogTemp, Log, TEXT("-------------------------------- Component : nullptr")); Component_A = nullptr; Component_B = nullptr; Component_C = nullptr; Component_D[0] = nullptr; OutputPointer(); } if(KeyPress&(1<<4)) { UE_LOG(LogTemp, Log, TEXT("-------------------------------- Actor : nullptr")); Actor_A = nullptr; Actor_B = nullptr; Actor_C = nullptr; Actor_D[0] = nullptr; OutputPointer(); } if(KeyPress&(1<<5)) { UE_LOG(LogTemp, Log, TEXT("-------------------------------- CPPObj : delete")); delete CPPObj_A; delete CPPObj_D[0]; OutputPointer(); } if(KeyPress&(1<<6)) { UE_LOG(LogTemp, Log, TEXT("-------------------------------- UObject : ConditionalBeginDestroy()")); UObject_A ->ConditionalBeginDestroy(); UObject_B ->ConditionalBeginDestroy(); UObject_C ->ConditionalBeginDestroy(); UObject_D[0]->ConditionalBeginDestroy(); OutputPointer(); } if(KeyPress&(1<<7)) { UE_LOG(LogTemp, Log, TEXT("-------------------------------- Component : DestroyComponent()")); Component_A->DestroyComponent(); Component_B->DestroyComponent(); Component_C->DestroyComponent(); Component_D[0]->DestroyComponent(); OutputPointer(); } if(KeyPress&(1<<8)) { UE_LOG(LogTemp, Log, TEXT("-------------------------------- Actor : Destroy()")); Actor_A->Destroy(); Actor_B->Destroy(); Actor_C->Destroy(); Actor_D[0]->Destroy(); OutputPointer(); } } };