Skip to content

Instantly share code, notes, and snippets.

@HassakuTb
Created April 12, 2022 07:47
Show Gist options
  • Save HassakuTb/d5e379dee84557957148024560abae12 to your computer and use it in GitHub Desktop.
Save HassakuTb/d5e379dee84557957148024560abae12 to your computer and use it in GitHub Desktop.
assist to construct my own PlayerLoop
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.LowLevel;
namespace HassakuLab.Utils.PlayerLoops
{
/// <summary>
/// utility to insert PlayerLoop subsystem
/// </summary>
public static class PlayerLoopInserter
{
public enum InsertMode
{
BeforeTarget,
AfterTarget,
First,
Last,
}
/// <summary>
/// search for PlayerLoopSystem matching the specified
/// </summary>
/// <param name="found">output: matched subsystem</param>
/// <param name="playerLoop">subjects to search</param>
/// <param name="target">target type of searching PlayerLoopSystem</param>
/// <returns>true: found, false: not found</returns>
private static bool SearchForSubsystem(out PlayerLoopSystem found, PlayerLoopSystem playerLoop, Type target)
{
if (playerLoop.type == target)
{
found = playerLoop;
return true;
}
if (playerLoop.subSystemList != null)
{
foreach (var system in playerLoop.subSystemList)
{
if (SearchForSubsystem(out var tmp, system, target))
{
found = tmp;
return true;
}
}
}
found = new PlayerLoopSystem();
return false;
}
/// <summary>
/// construct new PlayerLoopSystem using new delegate
/// </summary>
/// <param name="subsystemToInsert">type of subsystem to insert</param>
/// <param name="parent">which subsystem insert into</param>
/// <param name="insertTarget">where to insert (only work with InsertType.BeforeTarget or InsertType.AfterTarget)</param>
/// <param name="updateDelegate">function invoke every frame</param>
/// <param name="insertMode">type of insert</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
private static PlayerLoopSystem ConstructInserted(
Type subsystemToInsert,
PlayerLoopSystem parent,
Type insertTarget,
PlayerLoopSystem.UpdateFunction updateDelegate,
InsertMode insertMode
)
{
PlayerLoopSystem insertItem = new PlayerLoopSystem
{
type = subsystemToInsert,
subSystemList = Array.Empty<PlayerLoopSystem>(),
updateDelegate = updateDelegate,
};
List<PlayerLoopSystem> brothers = parent.subSystemList != null
? parent.subSystemList.ToList()
: new List<PlayerLoopSystem>();
switch (insertMode)
{
case InsertMode.AfterTarget:
case InsertMode.BeforeTarget:
int targetIndex = brothers.FindIndex(target => target.type == insertTarget);
if (targetIndex == -1) throw new ArgumentException($"there is no type ({insertTarget.Name}) in ({parent.type.Name})");
int insertIndex = insertMode switch
{
InsertMode.AfterTarget => targetIndex + 1,
InsertMode.BeforeTarget => targetIndex,
// ReSharper disable once UnreachableSwitchArmDueToIntegerAnalysis
_ => 0,
};
brothers.Insert(insertIndex, insertItem);
break;
case InsertMode.First:
brothers.Insert(0, insertItem);
break;
case InsertMode.Last:
brothers.Add(insertItem);
break;
default:
throw new ArgumentOutOfRangeException(nameof(insertMode), insertMode, null);
}
PlayerLoopSystem newSystem = parent;
newSystem.subSystemList = brothers.ToArray();
return newSystem;
}
/// <summary>
/// replace subsystem with existing
/// </summary>
/// <param name="playerLoop">reference to target PlayerLoopSystem</param>
/// <param name="replaceTo">item replace to</param>
/// <returns>true: finish with replace, false: finish without replace</returns>
private static bool ReplaceSubsystem(ref PlayerLoopSystem playerLoop, PlayerLoopSystem replaceTo)
{
if (playerLoop.type == replaceTo.type)
{
playerLoop = replaceTo;
return true;
}
if (playerLoop.subSystemList != null)
{
for (int i = 0; i < playerLoop.subSystemList.Length; ++i)
{
if (ReplaceSubsystem(ref playerLoop.subSystemList[i], replaceTo))
{
return true;
}
}
}
return false;
}
/// <summary>
/// insert subsystem to PlayerLoop
/// </summary>
/// <param name="subsystemToInsert">type of subsystem to insert</param>
/// <param name="parent">which subsystem insert into</param>
/// <param name="insertTarget">where to insert (only work with InsertType.BeforeTarget or InsertType.AfterTarget)</param>
/// <param name="updateDelegate">delegate that will invoke every frame</param>
/// <param name="insertMode">type of insert</param>
/// <exception cref="ArgumentException"></exception>
public static void InsertSubsystem(
Type subsystemToInsert,
Type parent,
Type insertTarget,
PlayerLoopSystem.UpdateFunction updateDelegate,
InsertMode insertMode
)
{
PlayerLoopSystem playerLoop = PlayerLoop.GetCurrentPlayerLoop();
// search for parent subsystem
if (!SearchForSubsystem(out var parentSubsystem, playerLoop, parent))
{
throw new ArgumentException($"there is no type ({parent.Name}) in PlayerLoop");
}
// construct subsystem to replace
PlayerLoopSystem newSystem = ConstructInserted(subsystemToInsert, parentSubsystem, insertTarget, updateDelegate, insertMode);
// replace subsystem
if (!ReplaceSubsystem(ref playerLoop, newSystem))
{
throw new ArgumentException($"failed to replace {parent.Name} to {newSystem.type.Name}");
}
PlayerLoop.SetPlayerLoop(playerLoop);
}
}
}
using UnityEngine;
using UnityEngine.PlayerLoop;
namespace HassaluLab.Utils.PlayerLoops.Samples{
public class SampleUsage : MonoBehaviour
{
private struct MyUpdate {}
private static void MyUpdateFunction()
{
// do something
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void RegisterSubsystem()
{
PlayerLoopInserter.InsertSubsystem(
subsystemToInsert: typeof(MyUpdate),
parent: typeof(Update),
insertTarget: typeof(Update.ScriptRunBehaviourUpdate),
updateDelegate: MyUpdateFunction,
insertMode: PlayerLoopInserter.InsertMode.BeforeTarget
);
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment