// zlib/libpng License
//
// Copyright (c) 2018 Sinoa
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.LowLevel;
namespace IceMilkTea.Core
{
///
/// PlayerLoopSystem構造体の内容をクラスとして表現されPlayerLoopSystemの順序を操作するための機能を提供しています
///
public class ImtPlayerLoopSystem
{
///
/// ループシステムの検索で、対象のループシステムを見つけられなかったときに返す値です
///
public const int LoopSystemNotFoundValue = -1;
// メンバ変数定義
private List subLoopSystemList;
private PlayerLoopSystem.UpdateFunction updateDelegate;
private IntPtr updateFunction;
private IntPtr loopConditionFunction;
///
/// この LoopSystem を表す型
///
public Type Type { get; private set; }
///
/// この LoopSystem が持つ LoopSystem
///
public IReadOnlyList SubLoopSystemList { get; }
#region 初期化&終了実装
///
/// クラスの初期化を行います
///
static ImtPlayerLoopSystem()
{
// アプリケーション終了イベントを登録する
Application.quitting += OnApplicationQuit;
}
///
/// Unityがアプリケーションの終了をする時に呼び出されます
///
private static void OnApplicationQuit()
{
//イベントの登録を解除してUnityの既定ループ機構に戻す
Application.quitting -= OnApplicationQuit;
PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop());
}
///
/// 指定されたPlayerLoopSystem構造体オブジェクトから値をコピーしてインスタンスの初期化を行います。
/// また、指定されたPlayerLoopSystem構造体オブジェクトにサブループシステムが存在する場合は再帰的にインスタンスの初期化が行われます。
///
/// コピー元になるPlayerLoopSystem構造体オブジェクトへの参照
public ImtPlayerLoopSystem(ref PlayerLoopSystem originalPlayerLoopSystem)
{
// 参照元から値を引っ張って初期化する
Type = originalPlayerLoopSystem.type;
updateDelegate = originalPlayerLoopSystem.updateDelegate;
updateFunction = originalPlayerLoopSystem.updateFunction;
loopConditionFunction = originalPlayerLoopSystem.loopConditionFunction;
subLoopSystemList = new List();
SubLoopSystemList = subLoopSystemList.AsReadOnly();
// もしサブシステムが有効な数で存在するなら
if (originalPlayerLoopSystem.subSystemList != null && originalPlayerLoopSystem.subSystemList.Length > 0)
{
// 再帰的にコピーを生成する
subLoopSystemList.AddRange(originalPlayerLoopSystem.subSystemList.Select(original => new ImtPlayerLoopSystem(ref original)));
}
}
///
/// 指定された型と更新関数でインスタンスの初期化を行います
///
/// 生成するPlayerLoopSystemの型
/// 生成するPlayerLoopSystemの更新関数。更新関数が不要な場合はnullの指定が可能です
/// type が null です
/// updateDelegate が null です
public ImtPlayerLoopSystem(Type type, PlayerLoopSystem.UpdateFunction updateDelegate)
{
// シンプルに初期化をする
this.Type = type ?? throw new ArgumentNullException(nameof(type));
this.updateDelegate = updateDelegate ?? throw new ArgumentNullException(nameof(updateDelegate));
subLoopSystemList = new List();
}
///
/// 指定されたアップデータを動かすPlayerLoopSystemのインスタンスを初期化します
///
/// PlayerLoopSystemによって動作させるアップデータ
/// updater が null です
public ImtPlayerLoopSystem(PlayerLoopUpdater updater)
{
// シンプルに初期化をする
Type = (updater ?? throw new ArgumentNullException(nameof(updater))).GetType();
updateDelegate = updater.Update;
subLoopSystemList = new List();
}
#endregion
#region Unity変換関数群
///
/// Unityの標準プレイヤーループを ImtPlayerLoopSystem として取得します
///
/// Unityの標準プレイヤーループをImtPlayerLoopSystemにキャストされた結果を返します
public static ImtPlayerLoopSystem GetDefaultPlayerLoop()
{
// キャストして返すだけ
return (ImtPlayerLoopSystem)PlayerLoop.GetDefaultPlayerLoop();
}
///
/// Unityの現在設定されているプレイヤーループを ImtPlayerLoopSystem として取得します
///
///
public static ImtPlayerLoopSystem GetCurrentPlayerLoop()
{
// キャストして返すだけ
return (ImtPlayerLoopSystem)PlayerLoop.GetCurrentPlayerLoop();
}
///
/// このインスタンスを本来の構造へ構築し、Unityのプレイヤーループへ設定します
///
public void BuildAndSetUnityPlayerLoop()
{
// 自身をキャストして設定するだけ
PlayerLoop.SetPlayerLoop((PlayerLoopSystem)this);
}
#endregion
#region コントロール関数群
///
/// 指定された型の更新ループに対して、ループシステムをタイミングの位置に挿入します
///
/// これから挿入するループシステムの挿入起点となる更新型
/// T で指定された更新ループを起点にどのタイミングで挿入するか
/// 挿入するループシステム
/// loopSystemがnullです
/// 対象のループシステムが挿入された場合はtrueを、挿入されなかった場合はfalseを返します
public bool Insert(InsertTiming timing, ImtPlayerLoopSystem loopSystem)
{
// 再帰検索を有効にして本来の挿入関数を叩く
return Insert(timing, loopSystem, true);
}
///
/// 指定された型の更新ループに対して、ループシステムをタイミングの位置に挿入します
///
/// これから挿入するループシステムの挿入起点となる更新型
/// T で指定された更新ループを起点にどのタイミングで挿入するか
/// 挿入するループシステム
/// 対象の型の検索を再帰的に行うかどうか
/// loopSystemがnullです
/// 対象のループシステムが挿入された場合はtrueを、挿入されなかった場合はfalseを返します
public bool Insert(InsertTiming timing, ImtPlayerLoopSystem loopSystem, bool recursiveSearch)
{
// ループシステムがnullなら
if (loopSystem == null)
{
// nullの挿入は許されない!
throw new ArgumentNullException(nameof(loopSystem));
}
// 挿入するインデックス値を探すが見つけられなかったら
var insertIndex = IndexOf();
if (insertIndex == LoopSystemNotFoundValue)
{
// もし再帰的に調べるのなら
if (recursiveSearch)
{
// 自身のサブループシステム分回る
foreach (var subLoopSystem in subLoopSystemList)
{
// サブループシステムに対して挿入を依頼して成功したのなら
if (subLoopSystem.Insert(timing, loopSystem, recursiveSearch))
{
// 成功を返す
return true;
}
}
}
// やっぱり駄目だったよ
return false;
}
// 検索結果を見つけたのなら、挿入タイミングによってインデックス値を調整して挿入後、成功を返す
insertIndex = timing == InsertTiming.BeforeInsert ? insertIndex : insertIndex + 1;
subLoopSystemList.Insert(insertIndex, loopSystem);
return true;
}
///
/// 指定された型の更新ループをサブループシステムから削除します
///
/// 削除する更新ループの型
/// 対象の型を再帰的に検索し削除するかどうか
/// 対象のループシステムが削除された場合はtrueを、削除されなかった場合はfalseを返します
public bool Remove(bool recursiveSearch)
{
// 削除するインデックス値を探すが見つけられなかったら
var removeIndex = IndexOf();
if (removeIndex == LoopSystemNotFoundValue)
{
// もし再帰的に調べるのなら
if (recursiveSearch)
{
// 自身のサブループシステム分回る
foreach (var subLoopSystem in subLoopSystemList)
{
// サブループシステムに対して削除依頼して成功したのなら
if (subLoopSystem.Remove(recursiveSearch))
{
// 成功を返す
return true;
}
}
}
// だめでした
return false;
}
// 対象インデックスの要素を殺す
subLoopSystemList.RemoveAt(removeIndex);
return true;
}
///
/// 指定された更新型でループシステムを探し出します。
///
/// 検索するループシステムの型
/// 対象の型の検索を再帰的に行うかどうか
/// 最初に見つけたループシステムを返しますが、見つけられなかった場合はnullを返します
public ImtPlayerLoopSystem Find(bool recursiveSearch)
{
// 自身のサブループシステムに該当の型があるか調べて、見つけたら
var result = subLoopSystemList.Find(loopSystem => loopSystem.Type == typeof(T));
if (result != null)
{
// 結果を返す
return result;
}
// 見つけられなく、かつ再帰検索でないのなら
if (result == null && !recursiveSearch)
{
// 諦めてnullを返す
return null;
}
// 自分のサブループシステムにも検索を問いかける
return subLoopSystemList.Find(loopSystem => loopSystem.Find(recursiveSearch) != null);
}
///
/// 指定された更新型で存在インデックス位置を取得します
///
/// 検索するループシステムの型
/// 最初に見つけたループシステムのインデックスを返しますが、見つけられなかった場合は ImtPlayerLoopSystem.LoopSystemNotFoundValue をかえします
public int IndexOf()
{
// 自身のサブループシステムに該当の型があるか調べるが、見つけられなかったら
var result = subLoopSystemList.FindIndex(loopSystem => loopSystem.Type == typeof(T));
if (result == -1)
{
// 見つけられなかったことを返す
return LoopSystemNotFoundValue;
}
// 見つけた位置を返す
return result;
}
#endregion
#region オペレータ&ToStringオーバーライド
///
/// PlayerLoopSystemからImtPlayerLoopSystemへキャストします
///
/// キャストする元になるPlayerLoopSystem
public static explicit operator ImtPlayerLoopSystem(PlayerLoopSystem original)
{
// 渡されたPlayerLoopSystemからImtPlayerLoopSystemのインスタンスを生成して返す
return new ImtPlayerLoopSystem(ref original);
}
///
/// ImtPlayerLoopSystemからPlayerLoopSystemへキャストします
///
/// キャストする元になるImtPlayerLoopSystem
public static explicit operator PlayerLoopSystem(ImtPlayerLoopSystem klass)
{
// 渡されたImtPlayerLoopSystemからPlayerLoopSystemへ変換する関数を叩いて返す
return klass.ToPlayerLoopSystem();
}
///
/// クラス化されているPlayerLoopSystemを構造体のPlayerLoopSystemへ変換します。
/// また、サブループシステムを保持している場合はサブループシステムも構造体のインスタンスが新たに生成され、初期化されます。
///
/// 内部コンテキストのコピーを行ったPlayerLoopSystemを返します
private PlayerLoopSystem ToPlayerLoopSystem()
{
// 新しいPlayerLoopSystem構造体のインスタンスを生成して初期化を行った後返す
return new PlayerLoopSystem()
{
// 各パラメータのコピー(サブループシステムも再帰的に構造体へインスタンス化)
type = Type,
updateDelegate = updateDelegate,
updateFunction = updateFunction,
loopConditionFunction = loopConditionFunction,
subSystemList = subLoopSystemList.Select(source => source.ToPlayerLoopSystem()).ToArray(),
};
}
///
/// ImpPlayerLoopSystem内のLoopSystem階層表示を文字列へ変換します
///
/// このインスタンスのLoopSystem階層状況を文字列化したものを返します
public override string ToString()
{
// バッファを用意してループシステムツリーの内容をダンプして結果を返す
var buffer = new StringBuilder();
DumpLoopSystemTree(buffer, string.Empty);
return buffer.ToString();
}
///
/// ImpPlayerLoopSystem内のLoopSystem階層を再帰的にバッファへ文字列を追記します
///
/// 追記対象のバッファ
/// 現在のインデントスペース
private void DumpLoopSystemTree(StringBuilder buffer, string indentSpace)
{
// 自分の名前からぶら下げツリー表記
buffer.Append($"{indentSpace}[{(Type == null ? "NULL" : Type.Name)}]\n");
foreach (var subSystem in subLoopSystemList)
{
// 新しいインデントスペース文字列を用意して自分の子にダンプさせる
subSystem.DumpLoopSystemTree(buffer, indentSpace + " ");
}
}
#endregion
}
}