/**
* Copyright(c) Live2D Inc. All rights reserved.
*
* Use of this source code is governed by the Live2D Open Software license
* that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
*/
using Live2D.Cubism.Framework;
using System;
using UnityEngine;
#if UNITY_2019_3_OR_NEWER
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;
#elif UNITY_2018_1_OR_NEWER
using UnityEngine.Experimental.LowLevel;
using UnityEngine.Experimental.PlayerLoop;
#endif
namespace Live2D.Cubism.Core
{
///
/// Runtime Cubism model.
///
[ExecuteInEditMode, CubismDontMoveOnReimport]
public sealed class CubismModel : MonoBehaviour
{
#region Delegates
///
/// Handler for .
///
/// Model the dymanic data applies to.
/// New data.
public delegate void DynamicDrawableDataHandler(CubismModel sender, CubismDynamicDrawableData[] data);
#endregion
#region Events
///
/// Event triggered if new is available for instance.
///
public event DynamicDrawableDataHandler OnDynamicDrawableData;
#endregion
#region Factory Methods
///
/// Instantiates a .
///
/// Cubism moc to instantiate.
/// Instance.
public static CubismModel InstantiateFrom(CubismMoc moc)
{
// Return if argument is invalid.
if (moc == null)
{
return null;
}
// Create model.
var model = new GameObject(moc.name)
.AddComponent();
// Initialize it by resetting it.
model.Reset(moc);
return model;
}
#endregion
///
/// Resets a reference in .
///
/// Target Cubism model.
/// Cubism moc to reset.
public static void ResetMocReference(CubismModel model, CubismMoc moc)
{
model.Moc = moc;
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private CubismMoc _moc;
///
/// Moc the instance was instantiated from.
///
public CubismMoc Moc
{
get { return _moc; }
private set { _moc = value; }
}
///
/// TaskableModel for unmanaged backend.
///
private CubismTaskableModel TaskableModel { get; set; }
///
/// backing field.
///
[NonSerialized]
private CubismParameter[] _parameters;
///
/// Drawables of model.
///
public CubismParameter[] Parameters
{
get
{
if (_parameters == null)
{
Revive();
}
return _parameters;
}
private set { _parameters = value; }
}
///
/// backing field.
///
[NonSerialized]
private CubismPart[] _parts;
///
/// Drawables of model.
///
public CubismPart[] Parts
{
get
{
if (_parts == null)
{
Revive();
}
return _parts;
}
private set { _parts = value; }
}
///
/// backing field.
///
[NonSerialized]
private CubismDrawable[] _drawables;
///
/// Drawables of model.
///
public CubismDrawable[] Drawables
{
get
{
if (_drawables == null)
{
Revive();
}
return _drawables;
}
private set { _drawables = value; }
}
///
/// backing field.
///
[NonSerialized]
private CubismCanvasInformation _canvasInformation;
///
/// Canvas information of model.
///
public CubismCanvasInformation CanvasInformation
{
get
{
if (_canvasInformation == null)
{
Revive();
}
return _canvasInformation;
}
private set { _canvasInformation = value; }
}
///
/// Parameter store cache.
///
CubismParameterStore _parameterStore;
///
/// True if instance is revived.
///
public bool IsRevived
{
get { return TaskableModel != null; }
}
///
/// True if instance can revive.
///
private bool CanRevive
{
get { return Moc != null; }
}
#if UNITY_2018_1_OR_NEWER
///
/// Model update functions for player loop.
///
[NonSerialized]
private static Action _modelUpdateFunctions;
private bool WasAttachedModelUpdateFunction { get; set; }
#endif
///
/// True on the frame the instance was enabled.
///
private bool WasJustEnabled { get; set; }
///
/// Frame number last update was done.
///
private int LastTick { get; set; }
///
/// Revives instance.
///
private void Revive()
{
// Return if already revive.
if (IsRevived)
{
return;
}
// Return if revive isn't possible.
if (!CanRevive)
{
return;
}
// Revive unmanaged model.
TaskableModel = new CubismTaskableModel(Moc);
// Revive proxies.
Parameters = GetComponentsInChildren();
Parts = GetComponentsInChildren();
Drawables = GetComponentsInChildren();
Parameters.Revive(TaskableModel.UnmanagedModel);
Parts.Revive(TaskableModel.UnmanagedModel);
Drawables.Revive(TaskableModel.UnmanagedModel);
CanvasInformation = new CubismCanvasInformation(TaskableModel.UnmanagedModel);
_parameterStore = GetComponent();
}
///
/// Initializes instance for first use.
///
/// Moc to instantiate from.
private void Reset(CubismMoc moc)
{
Moc = moc;
name = moc.name;
TaskableModel = new CubismTaskableModel(moc);
// Create and initialize proxies.
var parameters = CubismParameter.CreateParameters(TaskableModel.UnmanagedModel);
var parts = CubismPart.CreateParts(TaskableModel.UnmanagedModel);
var drawables = CubismDrawable.CreateDrawables(TaskableModel.UnmanagedModel);
parameters.transform.SetParent(transform);
parts.transform.SetParent(transform);
drawables.transform.SetParent(transform);
Parameters = parameters.GetComponentsInChildren();
Parts = parts.GetComponentsInChildren();
Drawables = drawables.GetComponentsInChildren();
CanvasInformation = new CubismCanvasInformation(TaskableModel.UnmanagedModel);
}
///
/// Forces update.
///
public void ForceUpdateNow()
{
WasJustEnabled = true;
LastTick = -1;
Revive();
#if UNITY_2018_1_OR_NEWER
OnModelUpdate();
#else
OnRenderObject();
#endif
}
#if UNITY_2018_1_OR_NEWER
///
/// Calls model update functions for player loop.
///
private static void OnModelsUpdate()
{
if (_modelUpdateFunctions != null)
{
_modelUpdateFunctions.Invoke();
}
}
///
/// Register the model update function into the player loop.
///
[RuntimeInitializeOnLoadMethod]
private static void RegisterCallbackFunction()
{
// Prepare the function for using player loop.
var myPlayerLoopSystem = new PlayerLoopSystem()
{
type = typeof(CubismModel), // Identifier for Profiler Hierarchy view.
updateDelegate = OnModelsUpdate // Register the function.
};
// Get the default player loop.
var playerLoopSystem =
#if UNITY_2019_3_OR_NEWER
PlayerLoop.GetCurrentPlayerLoop();
#else
PlayerLoop.GetDefaultPlayerLoop();
#endif
var playerLoopIndex = -1;
for (var i = 0; i < playerLoopSystem.subSystemList.Length; i++)
{
if (playerLoopSystem.subSystemList[i].type != typeof(PreLateUpdate))
{
continue;
}
playerLoopIndex = i;
break;
}
if (playerLoopIndex < 0)
{
Debug.LogError("CubismModel : Failed to add processing to PlayerLoop.");
return;
}
// Get the "PreLateUpdate" system.
var playerLoopSubSystem = playerLoopSystem.subSystemList[playerLoopIndex];
var subSystemList = playerLoopSubSystem.subSystemList;
// Register the model update function after "PreLateUpdate" system.
Array.Resize(ref subSystemList, subSystemList.Length + 1);
subSystemList[subSystemList.Length - 1] = myPlayerLoopSystem;
// Restore the "PreLateUpdate" sytem.
playerLoopSubSystem.subSystemList = subSystemList;
playerLoopSystem.subSystemList[playerLoopIndex] = playerLoopSubSystem;
PlayerLoop.SetPlayerLoop(playerLoopSystem);
}
#endif
#region Unity Event Handling
///
/// Called by Unity. Triggers to update.
///
private void Update()
{
#if UNITY_2018_1_OR_NEWER
if (!WasAttachedModelUpdateFunction)
{
_modelUpdateFunctions += OnModelUpdate;
WasAttachedModelUpdateFunction = true;
}
#endif
// Return on first frame enabled.
if (WasJustEnabled)
{
return;
}
// Return unless revived.
if (!IsRevived)
{
return;
}
// Return if backend is ticking.
if (!TaskableModel.DidExecute)
{
return;
}
// Sync parameters back.
TaskableModel.TryReadParameters(Parameters);
// restore last frame parameters value and parts opacity.
if (_parameterStore != null)
{
_parameterStore.RestoreParameters();
}
// Trigger event.
if (OnDynamicDrawableData == null)
{
return;
}
OnDynamicDrawableData(this, TaskableModel.DynamicDrawableData);
}
///
/// Called by Unity. Blockingly updates on first frame enabled; otherwise tries async update.
///
private void OnRenderObject()
{
#if !UNITY_2018_1_OR_NEWER
OnModelUpdate();
#endif
}
///
/// Update model states.
///
private void OnModelUpdate()
{
// Return unless revived.
if (!IsRevived)
{
return;
}
// Return if already ticked this frame.
if (LastTick == Time.frameCount && Application.isPlaying)
{
return;
}
LastTick = Time.frameCount;
// Try to sync parameters and parts (without caring whether task is executing or not).
TaskableModel.TryWriteParametersAndParts(Parameters, Parts);
// Return if task is executing.
if (TaskableModel.IsExecuting)
{
return;
}
// Force blocking update on first frame enabled.
if (WasJustEnabled)
{
// Force sync update.
TaskableModel.UpdateNow();
// Unset condition.
WasJustEnabled = false;
// Fetch results by calling own 'Update()'.
Update();
return;
}
// Enqueue update task.
TaskableModel.Update();
}
///
/// Called by Unity. Revives instance.
///
private void OnEnable()
{
WasJustEnabled = true;
Revive();
}
private void OnDisable()
{
#if UNITY_2018_1_OR_NEWER
if (WasAttachedModelUpdateFunction)
{
_modelUpdateFunctions -= OnModelUpdate;
WasAttachedModelUpdateFunction = false;
}
#endif
}
///
/// Called by Unity. Releases unmanaged memory.
///
private void OnDestroy()
{
if (!IsRevived)
{
return;
}
TaskableModel.ReleaseUnmanaged();
TaskableModel = null;
}
///
/// Called by Unity. Triggers .
///
private void OnValidate()
{
OnEnable();
}
#endregion
}
}