463 lines
15 KiB
C#
463 lines
15 KiB
C#
using Cryville.Common;
|
|
using Cryville.Common.Pdt;
|
|
using Cryville.Crtr.Config;
|
|
using Cryville.Input;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.Serialization;
|
|
using System.Threading;
|
|
using UnityEngine;
|
|
using UnityEngine.Profiling;
|
|
using RVector4 = UnityEngine.Vector4;
|
|
|
|
namespace Cryville.Crtr.Ruleset {
|
|
public class InputProxy : IDisposable {
|
|
readonly PdtEvaluator _etor;
|
|
readonly PdtRuleset _ruleset;
|
|
readonly Judge _judge;
|
|
readonly InputVector _screenSize;
|
|
public InputProxy(PdtRuleset ruleset, Judge judge, Vector2 screenSize) {
|
|
for (int i = 0; i <= MAX_DEPTH; i++) {
|
|
var vecsrc = new InputVectorSrc();
|
|
_vecsrcs[i] = vecsrc;
|
|
_vecops[i] = new InputVectorOp(vecsrc);
|
|
}
|
|
_etor = judge != null ? judge._etor : PdtEvaluator.Instance;
|
|
_ruleset = ruleset;
|
|
_judge = judge;
|
|
_screenSize = new InputVector(screenSize.x, screenSize.y);
|
|
foreach (var i in ruleset.inputs) {
|
|
_use.Add(i.Key, 0);
|
|
_rev.Add(i.Key, new List<Identifier>());
|
|
}
|
|
foreach (var i in ruleset.inputs) {
|
|
if (i.Value.pass != null) {
|
|
foreach (var p in i.Value.pass)
|
|
_rev[p.Key].Add(i.Key);
|
|
}
|
|
}
|
|
}
|
|
#region Settings
|
|
readonly Dictionary<Identifier, InputProxyEntry> _tproxies = new();
|
|
readonly Dictionary<InputSource, InputProxyEntry> _sproxies = new();
|
|
readonly Dictionary<Identifier, int> _use = new();
|
|
readonly Dictionary<Identifier, List<Identifier>> _rev = new();
|
|
public event EventHandler<ProxyChangedEventArgs> ProxyChanged;
|
|
public void LoadFrom(Dictionary<string, RulesetConfig.InputEntry> config) {
|
|
foreach (var cfg in config) {
|
|
var handler = Game.InputManager.GetHandlerByTypeName(cfg.Value.handler);
|
|
if (handler == null) {
|
|
Game.MainLogger.Log(3, "Input", "Uninitialized or unknown handler in ruleset config: {0}", cfg.Value.handler);
|
|
continue;
|
|
}
|
|
Set(new InputProxyEntry {
|
|
Target = new Identifier(cfg.Key),
|
|
Source = new InputSource {
|
|
Handler = handler,
|
|
Type = cfg.Value.type
|
|
}
|
|
});
|
|
}
|
|
}
|
|
public void SaveTo(Dictionary<string, RulesetConfig.InputEntry> config) {
|
|
config.Clear();
|
|
foreach (var p in _tproxies) {
|
|
config.Add((string)p.Key.Name, new RulesetConfig.InputEntry {
|
|
handler = p.Value.Source.Value.Handler.GetType().AssemblyQualifiedName,
|
|
type = p.Value.Source.Value.Type
|
|
});
|
|
}
|
|
}
|
|
public void Clear() {
|
|
_tproxies.Clear();
|
|
_sproxies.Clear();
|
|
foreach (var i in _rev) _use[i.Key] = 0;
|
|
}
|
|
public void Set(InputProxyEntry proxy) {
|
|
var target = proxy.Target;
|
|
if (!_ruleset.inputs.ContainsKey(target)) throw new ArgumentException("Invalid input name");
|
|
if (_tproxies.ContainsKey(target)) Remove(proxy);
|
|
if (_use[target] > 0)
|
|
throw new InvalidOperationException("Input already assigned");
|
|
if (proxy.Source != null) {
|
|
_tproxies.Add(target, proxy);
|
|
_sproxies.Add(proxy.Source.Value, proxy);
|
|
IncrementUseRecursive(target);
|
|
IncrementReversedUseRecursive(target);
|
|
}
|
|
}
|
|
void Remove(InputProxyEntry proxy) {
|
|
var target = proxy.Target;
|
|
_sproxies.Remove(_tproxies[target].Source.Value);
|
|
_tproxies.Remove(target);
|
|
DecrementUseRecursive(target);
|
|
DecrementReversedUseRecursive(target);
|
|
}
|
|
public bool IsUsed(InputSource src) {
|
|
return _sproxies.ContainsKey(src);
|
|
}
|
|
public bool IsCompleted() {
|
|
foreach (var i in _use)
|
|
if (!IsCompleted(i.Key)) return false;
|
|
return true;
|
|
}
|
|
bool IsCompleted(Identifier name) {
|
|
return name.Key == _var_pause || _use[name] != 0 || _tproxies.ContainsKey(name);
|
|
}
|
|
static readonly int _var_pause = IdentifierManager.Shared.Request("pause");
|
|
void IncrementUseRecursive(Identifier name) {
|
|
BroadcastProxyChanged(name);
|
|
var passes = _ruleset.inputs[name].pass;
|
|
if (passes != null) {
|
|
foreach (var p in _ruleset.inputs[name].pass) {
|
|
_use[p.Key]++;
|
|
IncrementUseRecursive(p.Key);
|
|
}
|
|
}
|
|
}
|
|
void IncrementReversedUseRecursive(Identifier name) {
|
|
foreach (var p in _rev[name]) {
|
|
_use[p]++;
|
|
BroadcastProxyChanged(p);
|
|
IncrementReversedUseRecursive(p);
|
|
}
|
|
}
|
|
void DecrementUseRecursive(Identifier name) {
|
|
BroadcastProxyChanged(name);
|
|
var passes = _ruleset.inputs[name].pass;
|
|
if (passes != null) {
|
|
foreach (var p in _ruleset.inputs[name].pass) {
|
|
_use[p.Key]--;
|
|
DecrementUseRecursive(p.Key);
|
|
}
|
|
}
|
|
}
|
|
void DecrementReversedUseRecursive(Identifier name) {
|
|
foreach (var p in _rev[name]) {
|
|
_use[p]--;
|
|
BroadcastProxyChanged(p);
|
|
DecrementReversedUseRecursive(p);
|
|
}
|
|
}
|
|
void BroadcastProxyChanged(Identifier name) {
|
|
ProxyChanged?.Invoke(this, this[name]);
|
|
}
|
|
public ProxyChangedEventArgs this[Identifier name] {
|
|
get {
|
|
return new ProxyChangedEventArgs(name, _tproxies.ContainsKey(name) ? _tproxies[name].Source : null, _use[name] > 0, !IsCompleted(name));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Handling
|
|
public void Activate() {
|
|
_vect.Clear(); _vecs.Clear();
|
|
foreach (var src in _sproxies) {
|
|
var isrc = src.Value.Source;
|
|
if (isrc != null) {
|
|
isrc.Value.Handler.OnInput += OnInput;
|
|
isrc.Value.Handler.OnBatch += OnBatch;
|
|
}
|
|
}
|
|
_targetActiveCount.Clear();
|
|
foreach (var i in _ruleset.inputs) {
|
|
if (i.Value.pass == null) _targetActiveCount.Add(i.Key, 0);
|
|
}
|
|
}
|
|
public void Deactivate() {
|
|
foreach (var src in _sproxies) {
|
|
var isrc = src.Value.Source;
|
|
if (isrc != null) {
|
|
isrc.Value.Handler.OnInput -= OnInput;
|
|
isrc.Value.Handler.OnBatch -= OnBatch;
|
|
}
|
|
}
|
|
}
|
|
|
|
~InputProxy() {
|
|
Dispose(false);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
public void Dispose() {
|
|
Dispose(true);
|
|
}
|
|
protected void Dispose(bool disposing) {
|
|
if (disposing) {
|
|
Deactivate();
|
|
foreach (var proxy in _tproxies) {
|
|
proxy.Value.Source.Value.Handler.OnInput -= OnInput;
|
|
}
|
|
}
|
|
}
|
|
|
|
static readonly int _var_input_vec = IdentifierManager.Shared.Request("input_vec");
|
|
const int MAX_DEPTH = 15;
|
|
const int MAX_DIMENSION = 4;
|
|
readonly InputVectorSrc[] _vecsrcs = new InputVectorSrc[MAX_DEPTH + 1];
|
|
readonly InputVectorOp[] _vecops = new InputVectorOp[MAX_DEPTH + 1];
|
|
unsafe class InputVectorSrc : PropSrc.FixedBuffer<RVector4> {
|
|
public InputVectorSrc() : base(PdtInternalType.Vector, MAX_DIMENSION * sizeof(float) + sizeof(int), null) {
|
|
fixed (byte* ptr = buf) {
|
|
*(int*)(ptr + MAX_DIMENSION * sizeof(float)) = PdtInternalType.Number;
|
|
}
|
|
}
|
|
public bool IsNull { get; set; }
|
|
public RVector4 Get() {
|
|
fixed (byte* _ptr = buf) {
|
|
return *(RVector4*)_ptr;
|
|
}
|
|
}
|
|
public void Set(RVector4 vec) {
|
|
fixed (byte* _ptr = buf) {
|
|
*(RVector4*)_ptr = vec;
|
|
}
|
|
Invalidate();
|
|
}
|
|
}
|
|
class InputVectorOp : PropOp {
|
|
readonly InputVectorSrc _src;
|
|
public InputVectorOp(InputVectorSrc src) {
|
|
_src = src;
|
|
}
|
|
protected override void Execute() {
|
|
var op = GetOperand(0);
|
|
if (op.Type == PdtInternalType.Null) {
|
|
_src.IsNull = true;
|
|
}
|
|
else {
|
|
var vec = new RVector4();
|
|
int dim;
|
|
if (op.Type == PdtInternalType.Number) dim = 1;
|
|
else if (op.Type == PdtInternalType.Vector) {
|
|
int _;
|
|
op.GetArraySuffix(out int arrtype, out _);
|
|
if (arrtype != PdtInternalType.Number)
|
|
throw new InvalidCastException("Not a vector of numbers");
|
|
dim = Math.Min(3, (op.Length - sizeof(int)) / sizeof(float));
|
|
}
|
|
else throw new InvalidCastException("Invalid vector");
|
|
for (int i = 0; i < dim; i++) {
|
|
vec[i] = op.AsNumber(i * sizeof(float));
|
|
}
|
|
_src.IsNull = false;
|
|
_src.Set(vec);
|
|
}
|
|
}
|
|
}
|
|
readonly Dictionary<InputHandler, double> _timeOrigins = new();
|
|
readonly Dictionary<Identifier, int> _targetActiveCount = new();
|
|
readonly Dictionary<InputIdentifier, float> _vect = new();
|
|
readonly Dictionary<ProxiedInputIdentifier, Vector4> _vecs = new();
|
|
double? _lockTime = null;
|
|
unsafe void OnInput(InputIdentifier id, InputFrame frame) {
|
|
var rc = id.Source.Handler.ReferenceCue;
|
|
if (rc.RelativeUnit == RelativeUnit.Pixel) {
|
|
frame = rc.InverseTransform(frame, _screenSize);
|
|
var vec = frame.Vector;
|
|
vec.X /= _screenSize.X; vec.Y /= _screenSize.Y;
|
|
vec.X -= 0.5f; vec.Y -= 0.5f;
|
|
vec.X *= ChartPlayer.hitRect.width; vec.Y *= ChartPlayer.hitRect.height;
|
|
frame.Vector = vec;
|
|
}
|
|
else frame = rc.InverseTransform(frame);
|
|
Monitor.Enter(_etor);
|
|
try {
|
|
Profiler.BeginSample("InputProxy.OnInput");
|
|
if (_sproxies.TryGetValue(id.Source, out InputProxyEntry proxy)) {
|
|
float tt = (float)GetSyncedTime(frame.Time, id.Source.Handler);
|
|
if (!_vect.TryGetValue(id, out float ft)) ft = tt;
|
|
_etor.ContextCascadeInsert();
|
|
try {
|
|
if (frame.IsNull) {
|
|
_etor.ContextCascadeUpdate(_var_input_vec, PropSrc.Null);
|
|
OnInput(id, proxy.Target, ft, tt, true);
|
|
}
|
|
else {
|
|
_vecsrcs[0].Set(new RVector4(frame.Vector.X, frame.Vector.Y, frame.Vector.Z, frame.Vector.W));
|
|
_etor.ContextCascadeUpdate(_var_input_vec, _vecsrcs[0]);
|
|
OnInput(id, proxy.Target, ft, tt, false);
|
|
}
|
|
}
|
|
finally {
|
|
_etor.ContextCascadeDiscard();
|
|
}
|
|
_vect[id] = tt;
|
|
}
|
|
}
|
|
finally {
|
|
Profiler.EndSample();
|
|
Monitor.Exit(_etor);
|
|
}
|
|
}
|
|
static readonly int _var_fv = IdentifierManager.Shared.Request("input_vec_from");
|
|
static readonly int _var_tv = IdentifierManager.Shared.Request("input_vec_to");
|
|
readonly InputVectorSrc _vecsrc = new();
|
|
unsafe void OnInput(InputIdentifier id, Identifier target, float ft, float tt, bool nullFlag, int depth = 0) {
|
|
if (depth >= MAX_DEPTH) throw new InputProxyException("Input propagation limit reached\nThe ruleset has invalid input definitions");
|
|
var def = _ruleset.inputs[target];
|
|
if (def.pass != null) {
|
|
foreach (var p in def.pass) {
|
|
_etor.ContextCascadeInsert();
|
|
try {
|
|
bool newNullFlag = nullFlag;
|
|
if (!newNullFlag) {
|
|
_etor.Evaluate(_vecops[depth + 1], p.Value);
|
|
newNullFlag = _vecsrcs[depth + 1].IsNull;
|
|
if (newNullFlag) _etor.ContextCascadeUpdate(_var_input_vec, PropSrc.Null);
|
|
else _etor.ContextCascadeUpdate(_var_input_vec, _vecsrcs[depth + 1]);
|
|
}
|
|
OnInput(id, p.Key, ft, tt, newNullFlag, depth + 1);
|
|
}
|
|
finally {
|
|
_etor.ContextCascadeDiscard();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
var pid = new ProxiedInputIdentifier { Source = id, Target = target };
|
|
PropSrc fv, tv = _etor.ContextCascadeLookup(_var_input_vec);
|
|
bool hfv;
|
|
if (hfv = _vecs.TryGetValue(pid, out RVector4 ifv)) {
|
|
_vecsrc.Set(ifv);
|
|
fv = _vecsrc;
|
|
}
|
|
else fv = PropSrc.Null;
|
|
if (fv.Type != PdtInternalType.Null || tv.Type != PdtInternalType.Null) {
|
|
if (fv.Type == PdtInternalType.Null) {
|
|
_targetActiveCount[target]++;
|
|
}
|
|
_etor.ContextCascadeInsert();
|
|
try {
|
|
_etor.ContextCascadeUpdate(_var_fv, fv);
|
|
_etor.ContextCascadeUpdate(_var_tv, tv);
|
|
_judge.Feed(target, ft, tt);
|
|
}
|
|
finally {
|
|
_etor.ContextCascadeDiscard();
|
|
}
|
|
if (tv.Type == PdtInternalType.Null) {
|
|
_vecs.Remove(pid);
|
|
_targetActiveCount[target]--;
|
|
}
|
|
else {
|
|
var itv = ((InputVectorSrc)tv).Get();
|
|
if (!hfv || ifv != itv) _vecs[pid] = itv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void OnBatch(InputHandler handler, double time) {
|
|
lock (_etor) {
|
|
foreach (var vec in _vecs) {
|
|
if (vec.Key.Source.Source.Handler != handler) continue;
|
|
if (!_sproxies.TryGetValue(vec.Key.Source.Source, out InputProxyEntry proxy)) continue;
|
|
|
|
float tt = (float)GetSyncedTime(time, handler);
|
|
if (_vect.TryGetValue(vec.Key.Source, out float ft) && ft < tt) {
|
|
_etor.ContextCascadeInsert();
|
|
_vecsrcs[0].Set(vec.Value);
|
|
_etor.ContextCascadeUpdate(_var_input_vec, _vecsrcs[0]);
|
|
OnInput(vec.Key.Source, proxy.Target, ft, tt, false);
|
|
_etor.ContextCascadeDiscard();
|
|
_vect[vec.Key.Source] = tt;
|
|
}
|
|
|
|
Cleanup(proxy.Target, tt, true);
|
|
}
|
|
}
|
|
}
|
|
void Cleanup(Identifier target, float tt, bool batching = false, int depth = 0) {
|
|
if (depth >= MAX_DEPTH) throw new InputProxyException("Input propagation limit reached\nThe ruleset has invalid input definitions");
|
|
var def = _ruleset.inputs[target];
|
|
if (def.pass != null) {
|
|
foreach (var p in def.pass) {
|
|
Cleanup(p.Key, tt, batching, depth + 1);
|
|
}
|
|
}
|
|
else if (batching || _targetActiveCount[target] == 0) {
|
|
_judge.Cleanup(target, tt);
|
|
}
|
|
}
|
|
public void SyncTime(double time) {
|
|
foreach (var s in _sproxies) {
|
|
var h = s.Key.Handler;
|
|
_timeOrigins[h] = h.GetCurrentTimestamp() - time;
|
|
}
|
|
}
|
|
const double batchDelayTolerance = 0.02;
|
|
public void ForceTick() {
|
|
lock (_etor) {
|
|
foreach (var s in _sproxies) {
|
|
var handler = s.Key.Handler;
|
|
Cleanup(s.Value.Target, (float)GetSyncedTime(handler.GetCurrentTimestamp() - batchDelayTolerance, handler));
|
|
}
|
|
}
|
|
}
|
|
public double GetTimestampAverage() {
|
|
double result = 0;
|
|
foreach (var s in _sproxies) {
|
|
var src = s.Key;
|
|
result += src.Handler.GetCurrentTimestamp() - _timeOrigins[src.Handler];
|
|
}
|
|
return result / _sproxies.Count;
|
|
}
|
|
double GetSyncedTime(double time, InputHandler handler) {
|
|
return _lockTime != null ? _lockTime.Value : (time - _timeOrigins[handler]);
|
|
}
|
|
public void LockTime() { _lockTime = GetTimestampAverage(); }
|
|
public void UnlockTime() { _lockTime = null; }
|
|
#endregion
|
|
}
|
|
|
|
public class ProxyChangedEventArgs : EventArgs {
|
|
public Identifier Name { get; private set; }
|
|
public InputSource? Proxy { get; private set; }
|
|
public bool Used { get; private set; }
|
|
public bool Required { get; private set; }
|
|
public ProxyChangedEventArgs(Identifier name, InputSource? src, bool used, bool required) {
|
|
Name = name;
|
|
Proxy = src;
|
|
Used = used;
|
|
Required = required;
|
|
}
|
|
}
|
|
|
|
public class InputProxyEntry {
|
|
public InputSource? Source { get; set; }
|
|
public Identifier Target { get; set; }
|
|
public byte[] Mapping { get; private set; }
|
|
}
|
|
|
|
public struct ProxiedInputIdentifier : IEquatable<ProxiedInputIdentifier> {
|
|
public InputIdentifier Source { get; set; }
|
|
public Identifier Target { get; set; }
|
|
public override readonly bool Equals(object obj) {
|
|
if (obj == null || obj is not ProxiedInputIdentifier other) return false;
|
|
return Equals(other);
|
|
}
|
|
public readonly bool Equals(ProxiedInputIdentifier other) {
|
|
return Source == other.Source && Target == other.Target;
|
|
}
|
|
public override readonly int GetHashCode() {
|
|
return Source.GetHashCode() ^ Target.GetHashCode();
|
|
}
|
|
public override readonly string ToString() {
|
|
return string.Format("{0}->{1}", Source, Target);
|
|
}
|
|
public static bool operator ==(ProxiedInputIdentifier lhs, ProxiedInputIdentifier rhs) {
|
|
return lhs.Equals(rhs);
|
|
}
|
|
public static bool operator !=(ProxiedInputIdentifier lhs, ProxiedInputIdentifier rhs) {
|
|
return !lhs.Equals(rhs);
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public class InputProxyException : Exception {
|
|
public InputProxyException() { }
|
|
public InputProxyException(string message) : base(message) { }
|
|
public InputProxyException(string message, Exception inner) : base(message, inner) { }
|
|
protected InputProxyException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
|
}
|
|
}
|