Files
crtr/Assets/Cryville/Crtr/Event/ContainerHandler.cs
2023-03-03 13:34:12 +08:00

268 lines
9.8 KiB
C#

using Cryville.Common;
using Cryville.Common.Buffers;
using Cryville.Crtr.Components;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Cryville.Crtr.Event {
public abstract class ContainerHandler : ISkinnableGroup {
#region Struct
public ContainerHandler() { }
/// <summary>
/// Prehandling <see cref="ContainerState"/>, prehandling the events.
/// </summary>
public ContainerState ps;
/// <summary>
/// Backward <see cref="ContainerState"/>, disposing events at the backward clipping plane, firing anchor state and temporary state.
/// </summary>
public ContainerState bs;
/// <summary>
/// Current <see cref="ContainerState"/>, handling events occuring at the current time.
/// </summary>
public ContainerState cs;
/// <summary>
/// Anchor <see cref="ContainerState"/>, computing the start position of the temporary state.
/// </summary>
public ContainerState ns;
/// <summary>
/// Temporary <see cref="ContainerState"/>, rendering events between the clipping planes.
/// </summary>
public ContainerState ts;
/// <summary>
/// <see cref="GameObject"/> group, the <see cref="Transform"/> containing all the generated elements in the <see cref="ContainerHandler"/>.
/// </summary>
protected Transform gogroup;
SkinComponent[] _comps;
public Vector3 Position { get; protected set; }
public Quaternion Rotation { get; protected set; }
public bool Alive { get; private set; }
bool PreGraphicalActive;
public void SetPreGraphicalActive(bool value, ContainerState s) {
if (PreGraphicalActive == value) return;
PreGraphicalActive = value;
if (PreGraphicalActive) StartPreGraphicalUpdate(s);
else EndPreGraphicalUpdate(s);
}
bool GraphicalActive;
public void SetGraphicalActive(bool value, ContainerState s) {
if (GraphicalActive == value) return;
GraphicalActive = value;
if (GraphicalActive) StartGraphicalUpdate(s);
else EndGraphicalUpdate(s);
}
public EventContainer Container {
get { return cs.Container; }
}
static readonly int _var_current_time = IdentifierManager.SharedInstance.Request("current_time");
static readonly int _var_invisible_bounds = IdentifierManager.SharedInstance.Request("invisible_bounds");
public readonly Dictionary<int, PropSrc> PropSrcs = new Dictionary<int, PropSrc>();
SkinContainer skinContainer;
protected Judge judge;
public void AttachSystems(PdtSkin skin, Judge judge) {
skinContainer = new SkinContainer(this, skin.elements);
this.judge = judge;
}
public readonly Dictionary<int, List<Anchor>> Anchors = new Dictionary<int, List<Anchor>>();
public readonly Dictionary<int, Anchor> DynamicAnchors = new Dictionary<int, Anchor>();
public readonly Dictionary<int, double> DynamicAnchorSetTime = new Dictionary<int, double>();
Anchor a_cur;
Anchor a_head;
Anchor a_tail;
static readonly int _a_cur = IdentifierManager.SharedInstance.Request("cur");
static readonly int _a_head = IdentifierManager.SharedInstance.Request("head");
static readonly int _a_tail = IdentifierManager.SharedInstance.Request("tail");
double atime_head;
double atime_tail;
public Anchor RegisterAnchor(int name, bool dyn = false, int propSrcCount = 0) {
var strname = IdentifierManager.SharedInstance.Retrieve(name);
var go = new GameObject("." + strname).transform;
go.SetParent(gogroup, false);
var result = new Anchor(name, go, propSrcCount);
if (dyn) {
if (DynamicAnchors.ContainsKey(name))
throw new ArgumentException(string.Format("The anchor \"{0}\" already exists", strname));
DynamicAnchors.Add(name, result);
DynamicAnchorSetTime.Add(name, double.NaN);
}
List<Anchor> list;
if (!Anchors.TryGetValue(name, out list))
Anchors.Add(name, list = new List<Anchor>());
list.Add(result);
return result;
}
protected void OpenAnchor(Anchor anchor) {
if (_openedAnchor != null) throw new InvalidOperationException("An anchor has been opened");
anchor.Transform.gameObject.SetActive(true);
_openedAnchor = anchor;
}
protected void CloseAnchor() {
_openedAnchor = null;
}
#endregion
#region Logic
#region Init methods: Called on prehandle
public virtual void PreInit() {
gogroup = new GameObject(TypeName + ":" + Container.GetHashCode().ToString(CultureInfo.InvariantCulture)).transform;
SkinContext = new SkinContext(gogroup);
if (cs.Parent != null)
gogroup.SetParent(cs.Parent.Handler.gogroup, false);
a_cur = RegisterAnchor(_a_cur);
a_head = RegisterAnchor(_a_head, true);
a_tail = RegisterAnchor(_a_tail, true);
}
public virtual void Init() {
ChartPlayer.etor.ContextState = ps;
ChartPlayer.etor.ContextEvent = Container;
skinContainer.MatchStatic();
ChartPlayer.etor.ContextEvent = null;
ChartPlayer.etor.ContextState = null;
_comps = gogroup.GetComponentsInChildren<SkinComponent>();
foreach (var i in _comps) i.Init();
}
public virtual void PostInit() {
PropSrcs.Add(_var_current_time, new PropSrc.Float(() => (float)cs.rootPrototype.Time));
PropSrcs.Add(_var_invisible_bounds, new PropSrc.Boolean(() => atime_head > atime_tail));
gogroup.gameObject.SetActive(false);
}
#endregion
#region Start methods
public virtual void StartPhysicalUpdate(ContainerState s) {
if (s.CloneType < 16) Alive = true;
else if (s.CloneType == 17) Init();
}
public virtual void StartLogicalUpdate(ContainerState s) { }
protected virtual void StartPreGraphicalUpdate(ContainerState s) { }
protected virtual void StartGraphicalUpdate(ContainerState s) {
if (gogroup) gogroup.gameObject.SetActive(true);
}
#endregion
public virtual void Update(ContainerState s, StampedEvent ev) {
if (s.CloneType == 3) SetPreGraphicalActive(true, s);
else if (ev is StampedEvent.Anchor) {
var tev = (StampedEvent.Anchor)ev;
if (gogroup) {
OpenAnchor(tev.Target);
#if UNITY_5_6_OR_NEWER
tev.Target.Transform.SetPositionAndRotation(Position, Rotation);
#else
tev.Target.Transform.position = Position;
tev.Target.Transform.rotation = Rotation;
#endif
MatchDynamic(s, 1);
CloseAnchor();
}
if (tev.Target == a_head) {
SetGraphicalActive(true, s);
}
else if (tev.Target == a_tail) {
SetGraphicalActive(false, s);
}
anchorEvPool.Return(tev);
}
else if (gogroup && s.CloneType == 2) MatchDynamic(s, 1);
}
#region End methods
protected virtual void EndGraphicalUpdate(ContainerState s) { }
protected virtual void EndPreGraphicalUpdate(ContainerState s) { }
public virtual void EndLogicalUpdate(ContainerState s) { }
public virtual void EndPhysicalUpdate(ContainerState s) { }
public virtual void Dispose() {
if (gogroup)
GameObject.Destroy(gogroup.gameObject);
Alive = false;
}
public virtual void DisposeAll() { }
#endregion
#region Utils
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static bool CanDoGraphicalUpdate(ContainerState s) { return s.CloneType >= 2 && s.CloneType < 16; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void MatchDynamic(ContainerState s, int dl) {
ChartPlayer.etor.ContextState = s;
ChartPlayer.etor.ContextEvent = Container;
skinContainer.MatchDynamic(dl);
ChartPlayer.etor.ContextEvent = null;
ChartPlayer.etor.ContextState = null;
}
#endregion
#region Anchor
public virtual void Anchor() {
foreach (var p in PropSrcs) p.Value.Invalidate();
foreach (var a in DynamicAnchors) DynamicAnchorSetTime[a.Key] = double.NaN;
atime_head = cs.StampedContainer.Time;
atime_tail = atime_head + cs.StampedContainer.Duration;
MatchDynamic(cs, 0);
foreach (var i in _comps) i.Tick(skinContainer, cs.Time);
if (cs.Active) PushAnchorEvent(cs.Time, a_cur);
if (double.IsNaN(DynamicAnchorSetTime[_a_head])) DynamicAnchorSetTime[_a_head] = atime_head;
if (double.IsNaN(DynamicAnchorSetTime[_a_tail])) DynamicAnchorSetTime[_a_tail] = atime_tail;
foreach (var t in DynamicAnchorSetTime) {
if (double.IsNaN(t.Value)) continue;
int priority = 0;
bool forced = true;
if (t.Key == _a_head) { priority = -1; }
else if (t.Key == _a_tail) { priority = 1; }
else forced = false;
PushAnchorEvent(t.Value, DynamicAnchors[t.Key], priority, forced);
}
foreach (var anchors in Anchors) foreach (var anchor in anchors.Value) anchor.Transform.gameObject.SetActive(false);
}
static readonly SimpleObjectPool<StampedEvent.Anchor> anchorEvPool
= new SimpleObjectPool<StampedEvent.Anchor>(1024);
void PushAnchorEvent(double time, Anchor anchor, int priority = 0, bool forced = false) {
var tev = anchorEvPool.Rent();
tev.Time = time;
tev.Container = Container;
tev.Target = anchor;
tev.CanDiscard = !forced;
tev.SetPriority(priority);
ts.Bus.PushTempEvent(tev);
}
public virtual void Discard(ContainerState s, StampedEvent ev) {
if (ev is StampedEvent.Anchor) {
anchorEvPool.Return((StampedEvent.Anchor)ev);
}
}
#endregion
#endregion
#region ISkinnableGroup
public abstract string TypeName { get; }
public SkinContext SkinContext { get; private set; }
Anchor _openedAnchor;
public int OpenedAnchorName { get { return _openedAnchor == null ? 0 : _openedAnchor.Name; } }
public bool TryGetAnchorsByName(int name, out IReadOnlyCollection<Anchor> result) {
List<Anchor> anchors;
var ret = Anchors.TryGetValue(name, out anchors);
result = anchors;
return ret;
}
void ISkinnableGroup.RegisterAnchor(int name) {
RegisterAnchor(name, true);
}
public void PushAnchorEvent(double time, int name) {
if (!DynamicAnchors.ContainsKey(name))
throw new ArgumentException(string.Format("Specified anchor \"{0}\" not found", IdentifierManager.SharedInstance.Retrieve(name)));
if (name == _a_head) atime_head = time;
else if (name == _a_tail) atime_tail = time;
DynamicAnchorSetTime[name] = time;
}
#endregion
}
}