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()); } 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 _tproxies = new(); readonly Dictionary _sproxies = new(); readonly Dictionary _use = new(); readonly Dictionary> _rev = new(); public event EventHandler ProxyChanged; public void LoadFrom(Dictionary 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 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 { 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 _timeOrigins = new(); readonly Dictionary _targetActiveCount = new(); readonly Dictionary _vect = new(); readonly Dictionary _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 { 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) { } } }