Code structure cleanup.
This commit is contained in:
466
Assets/Cryville/Crtr/Ruleset/InputProxy.cs
Normal file
466
Assets/Cryville/Crtr/Ruleset/InputProxy.cs
Normal file
@@ -0,0 +1,466 @@
|
||||
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 Logger = Cryville.Common.Logging.Logger;
|
||||
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 Dictionary<Identifier, InputProxyEntry>();
|
||||
readonly Dictionary<InputSource, InputProxyEntry> _sproxies = new Dictionary<InputSource, InputProxyEntry>();
|
||||
readonly Dictionary<Identifier, int> _use = new Dictionary<Identifier, int>();
|
||||
readonly Dictionary<Identifier, List<Identifier>> _rev = new Dictionary<Identifier, List<Identifier>>();
|
||||
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) {
|
||||
Logger.Log("main", 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) {
|
||||
var del = ProxyChanged;
|
||||
if (del != null) del(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 arrtype, _;
|
||||
op.GetArraySuffix(out 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 Dictionary<InputHandler, double>();
|
||||
readonly Dictionary<Identifier, int> _targetActiveCount = new Dictionary<Identifier, int>();
|
||||
readonly Dictionary<InputIdentifier, float> _vect = new Dictionary<InputIdentifier, float>();
|
||||
readonly Dictionary<ProxiedInputIdentifier, Vector4> _vecs = new Dictionary<ProxiedInputIdentifier, Vector4>();
|
||||
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");
|
||||
InputProxyEntry proxy;
|
||||
if (_sproxies.TryGetValue(id.Source, out proxy)) {
|
||||
float ft, tt = (float)GetSyncedTime(frame.Time, id.Source.Handler);
|
||||
if (!_vect.TryGetValue(id, out 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 InputVectorSrc();
|
||||
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; Vector4 ifv;
|
||||
if (hfv = _vecs.TryGetValue(pid, out 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;
|
||||
InputProxyEntry proxy;
|
||||
if (!_sproxies.TryGetValue(vec.Key.Source.Source, out proxy)) continue;
|
||||
|
||||
float ft, tt = (float)GetSyncedTime(time, handler);
|
||||
if (_vect.TryGetValue(vec.Key.Source, out 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 bool Equals(object obj) {
|
||||
if (obj == null || !(obj is ProxiedInputIdentifier)) return false;
|
||||
return Equals((ProxiedInputIdentifier)obj);
|
||||
}
|
||||
public bool Equals(ProxiedInputIdentifier other) {
|
||||
return Source == other.Source && Target == other.Target;
|
||||
}
|
||||
public override int GetHashCode() {
|
||||
return Source.GetHashCode() ^ Target.GetHashCode();
|
||||
}
|
||||
public override 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) { }
|
||||
}
|
||||
}
|
11
Assets/Cryville/Crtr/Ruleset/InputProxy.cs.meta
Normal file
11
Assets/Cryville/Crtr/Ruleset/InputProxy.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af16092e83ce23d46b46254e2d9798f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
460
Assets/Cryville/Crtr/Ruleset/Judge.cs
Normal file
460
Assets/Cryville/Crtr/Ruleset/Judge.cs
Normal file
@@ -0,0 +1,460 @@
|
||||
using Cryville.Common;
|
||||
using Cryville.Common.Buffers;
|
||||
using Cryville.Common.Collections.Generic;
|
||||
using Cryville.Common.Collections.Specialized;
|
||||
using Cryville.Common.Pdt;
|
||||
using Cryville.Crtr.Event;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Formatting;
|
||||
using UnityEngine;
|
||||
using UnsafeIL;
|
||||
|
||||
namespace Cryville.Crtr.Ruleset {
|
||||
internal struct JudgeResult {
|
||||
public float? Time { get; set; }
|
||||
public Vector4 Vector { get; set; }
|
||||
}
|
||||
internal class JudgeEvent {
|
||||
public double StartTime { get; set; }
|
||||
public double EndTime { get; set; }
|
||||
public double StartClip { get; set; }
|
||||
public double EndClip { get; set; }
|
||||
public Chart.Judge BaseEvent { get; set; }
|
||||
public JudgeDefinition Definition { get; set; }
|
||||
public NoteHandler Handler { get; set; }
|
||||
public JudgeResult JudgeResult { get; set; }
|
||||
public JudgeCallContext CallContext { get; set; }
|
||||
}
|
||||
internal struct JudgeCallContext {
|
||||
public bool CalledOnMiss { get; set; }
|
||||
public float CallTime { get; set; }
|
||||
public JudgeEvent ReturnEvent { get; set; }
|
||||
public int ReturnIndex { get; set; }
|
||||
}
|
||||
internal interface IJudge {
|
||||
void Call(JudgeEvent ev, float time, Identifier id, bool onMiss, int index);
|
||||
bool Pass(JudgeEvent ev, float time, Identifier[] ids, bool onMiss, int depth);
|
||||
void UpdateScore(ScoreOperation op, PdtExpression exp);
|
||||
}
|
||||
public class Judge : IJudge {
|
||||
#region Data
|
||||
readonly ChartPlayer _sys;
|
||||
internal readonly PdtEvaluator _etor;
|
||||
readonly PdtRuleset _rs;
|
||||
internal Dictionary<Identifier, PdtExpression> _areaFuncs;
|
||||
readonly Dictionary<Identifier, List<JudgeEvent>> evs
|
||||
= new Dictionary<Identifier, List<JudgeEvent>>();
|
||||
readonly Dictionary<Identifier, List<JudgeEvent>> activeEvs
|
||||
= new Dictionary<Identifier, List<JudgeEvent>>();
|
||||
static readonly int _var_pause = IdentifierManager.Shared.Request("pause");
|
||||
readonly JudgeDefinition _judgePause;
|
||||
static readonly IComparer<JudgeEvent> _stcmp = new JudgeEventStartTimeComparer();
|
||||
class JudgeEventStartTimeComparer : IComparer<JudgeEvent> {
|
||||
public int Compare(JudgeEvent x, JudgeEvent y) {
|
||||
return x.StartClip.CompareTo(y.StartClip);
|
||||
}
|
||||
}
|
||||
public Judge(ChartPlayer sys, PdtRuleset rs) {
|
||||
_sys = sys;
|
||||
_etor = new PdtEvaluator();
|
||||
_etor.ContextJudge = this;
|
||||
_rs = rs;
|
||||
_areaFuncs = rs.areas;
|
||||
_identop = new PropOp.Identifier(v => _identbuf = new Identifier(v));
|
||||
_clipop = new PropOp.Clip(v => _clipbuf = v);
|
||||
_rs.judges.TryGetValue(new Identifier(_var_pause), out _judgePause);
|
||||
foreach (var i in rs.inputs) {
|
||||
var id = i.Key;
|
||||
var l = new List<JudgeEvent>();
|
||||
evs.Add(id, l);
|
||||
activeEvs.Add(id, new List<JudgeEvent>());
|
||||
if (_judgePause != null && id.Key == _var_pause) {
|
||||
l.Add(new JudgeEvent {
|
||||
StartTime = double.NegativeInfinity, EndTime = double.PositiveInfinity,
|
||||
StartClip = double.NegativeInfinity, EndClip = double.PositiveInfinity,
|
||||
Definition = _judgePause,
|
||||
});
|
||||
}
|
||||
}
|
||||
InitJudges();
|
||||
InitScores();
|
||||
}
|
||||
Identifier _identbuf; readonly PropOp _identop;
|
||||
Clip _clipbuf; readonly PropOp _clipop;
|
||||
public void Prepare(StampedEvent sev, NoteHandler handler) {
|
||||
var tev = (Chart.Judge)sev.Unstamped;
|
||||
InsertEvent(tev, new Clip((float)sev.Time, (float)(sev.Time + sev.Duration)), tev.Id, handler);
|
||||
}
|
||||
void InsertEvent(Chart.Judge ev, Clip clip, Identifier id, NoteHandler handler, JudgeCallContext call = default(JudgeCallContext)) {
|
||||
if (id.Key == _var_pause) throw new InvalidOperationException("Cannot assign the special judge \"pause\" to notes");
|
||||
var def = _rs.judges[id];
|
||||
_etor.Evaluate(_identop, def.input);
|
||||
_etor.Evaluate(_clipop, def.clip);
|
||||
var list = evs[_identbuf];
|
||||
var jev = new JudgeEvent {
|
||||
StartTime = clip.Behind,
|
||||
EndTime = clip.Ahead,
|
||||
StartClip = clip.Behind + _clipbuf.Behind,
|
||||
EndClip = clip.Ahead + _clipbuf.Ahead,
|
||||
BaseEvent = ev,
|
||||
Definition = def,
|
||||
Handler = handler,
|
||||
CallContext = call,
|
||||
};
|
||||
var index = list.BinarySearch(jev, _stcmp);
|
||||
if (index < 0) index = ~index;
|
||||
list.Insert(index, jev);
|
||||
}
|
||||
#endregion
|
||||
#region Judge
|
||||
internal readonly IntKeyedDictionary<int> judgeMap = new IntKeyedDictionary<int>();
|
||||
void InitJudges() {
|
||||
foreach (var i in _rs.judges) {
|
||||
var id = i.Key;
|
||||
judgeMap.Add(id.Key, IdentifierManager.Shared.Request("judge_" + id.Name));
|
||||
}
|
||||
}
|
||||
static bool _flag;
|
||||
static readonly PropOp.Boolean _flagop = new PropOp.Boolean(v => _flag = v);
|
||||
static readonly HitOp _hitop = new HitOp();
|
||||
class HitOp : PdtOperator {
|
||||
const int MAX_SORTS = 16;
|
||||
readonly float[] _buf = new float[MAX_SORTS];
|
||||
readonly float[] _sorts = new float[MAX_SORTS];
|
||||
public bool LastHit { get; private set; }
|
||||
public JudgeResult JudgeResult { get; private set; }
|
||||
|
||||
public HitOp() : base(MAX_SORTS) { }
|
||||
|
||||
protected override unsafe void Execute() {
|
||||
LastHit = false;
|
||||
var judgeResult = new JudgeResult();
|
||||
var judgeVector = new Vector4();
|
||||
for (int i = 0; i < LoadedOperandCount; i++) {
|
||||
var op = GetOperand(i);
|
||||
var sort = op.AsNumber();
|
||||
if (sort <= 0) return;
|
||||
if (!LastHit) {
|
||||
if (sort < _sorts[i]) return;
|
||||
if (sort > _sorts[i]) LastHit = true;
|
||||
}
|
||||
_buf[i] = sort;
|
||||
if (op.Type == PdtInternalType.Vector) {
|
||||
var len = (op.Length - sizeof(int)) / sizeof(float);
|
||||
if (len > 1) {
|
||||
judgeResult.Time = op.AsNumber(sizeof(float));
|
||||
for (int j = 0; j < 4; j++) {
|
||||
if (len > j + 2) judgeVector[j] = op.AsNumber((j + 2) * sizeof(float));
|
||||
}
|
||||
judgeResult.Vector = judgeVector;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!LastHit) return;
|
||||
Array.Clear(_buf, LoadedOperandCount, MAX_SORTS - LoadedOperandCount);
|
||||
fixed (float* ptrsrc = _buf, ptrdest = _sorts) {
|
||||
Unsafe.CopyBlock(ptrdest, ptrsrc, MAX_SORTS * sizeof(float));
|
||||
}
|
||||
JudgeResult = judgeResult;
|
||||
}
|
||||
|
||||
public void Clear() { Array.Clear(_sorts, 0, MAX_SORTS); }
|
||||
}
|
||||
static readonly int _var_fn = IdentifierManager.Shared.Request("judge_time_from");
|
||||
static readonly int _var_tn = IdentifierManager.Shared.Request("judge_time_to");
|
||||
static readonly int _var_ft = IdentifierManager.Shared.Request("input_time_from");
|
||||
static readonly int _var_tt = IdentifierManager.Shared.Request("input_time_to");
|
||||
readonly PropStores.Float
|
||||
_numst1 = new PropStores.Float(),
|
||||
_numst2 = new PropStores.Float(),
|
||||
_numst3 = new PropStores.Float(),
|
||||
_numst4 = new PropStores.Float();
|
||||
|
||||
static readonly int _var_jt = IdentifierManager.Shared.Request("hit_time");
|
||||
static readonly int _var_jdt = IdentifierManager.Shared.Request("hit_delta_time");
|
||||
static readonly int _var_jv = IdentifierManager.Shared.Request("hit_vec");
|
||||
readonly PropStores.Float
|
||||
_jnumst = new PropStores.Float(),
|
||||
_jdnumst = new PropStores.Float();
|
||||
readonly PropStores.Vector4 _jvecst = new PropStores.Vector4();
|
||||
|
||||
// Adopted from System.Collections.Generic.ArraySortHelper<T>.InternalBinarySearch(T[] array, int index, int length, T value, IComparer<T> comparer)
|
||||
int BinarySearch(List<JudgeEvent> list, float time, int stack) {
|
||||
int num = 0;
|
||||
int num2 = list.Count - 1;
|
||||
while (num <= num2) {
|
||||
int num3 = num + (num2 - num >> 1);
|
||||
int num4 = -list[num3].Definition.stack.CompareTo(stack);
|
||||
if (num4 == 0) num4 = list[num3].StartClip.CompareTo(time);
|
||||
if (num4 == 0) return num3;
|
||||
else if (num4 < 0) num = num3 + 1;
|
||||
else num2 = num3 - 1;
|
||||
}
|
||||
return ~num;
|
||||
}
|
||||
int BinarySearchFirst(List<JudgeEvent> list, int stack) {
|
||||
if (list[0].Definition.stack == stack) return 0;
|
||||
int num = 0;
|
||||
int num2 = list.Count - 1;
|
||||
while (num <= num2) {
|
||||
int num3 = num + (num2 - num >> 1);
|
||||
int num4 = -list[num3].Definition.stack.CompareTo(stack);
|
||||
if (num4 > 0) num2 = num3 - 1;
|
||||
else if (num4 < 0) num = num3 + 1;
|
||||
else if (num != num3) num2 = num3;
|
||||
else return num;
|
||||
}
|
||||
return ~num;
|
||||
}
|
||||
void UpdateContextJudgeEvent(JudgeEvent ev) {
|
||||
_numst1.Value = (float)ev.StartTime; _etor.ContextCascadeUpdate(_var_fn, _numst1.Source);
|
||||
_numst2.Value = (float)ev.EndTime; _etor.ContextCascadeUpdate(_var_tn, _numst2.Source);
|
||||
if (ev.BaseEvent != null) {
|
||||
_etor.ContextEvent = ev.BaseEvent;
|
||||
_etor.ContextState = ev.Handler.cs;
|
||||
}
|
||||
var call = ev.CallContext;
|
||||
if (call.ReturnEvent != null) {
|
||||
JudgeResult judgeResult = call.ReturnEvent.JudgeResult;
|
||||
_jnumst.Value = judgeResult.Time.Value; _etor.ContextCascadeUpdate(_var_jt, _jnumst.Source);
|
||||
_jdnumst.Value = (float)(judgeResult.Time.Value - call.ReturnEvent.StartTime); _etor.ContextCascadeUpdate(_var_jdt, _jdnumst.Source);
|
||||
_jvecst.Value = judgeResult.Vector; _etor.ContextCascadeUpdate(_var_jv, _jvecst.Source);
|
||||
}
|
||||
else {
|
||||
_etor.ContextCascadeUpdate(_var_jt, PropSrc.Null);
|
||||
_etor.ContextCascadeUpdate(_var_jv, PropSrc.Null);
|
||||
}
|
||||
}
|
||||
public void Feed(Identifier target, float ft, float tt) {
|
||||
Forward(target, tt);
|
||||
var actlist = activeEvs[target];
|
||||
if (actlist.Count > 0) {
|
||||
_numst3.Value = ft; _etor.ContextCascadeUpdate(_var_ft, _numst3.Source);
|
||||
_numst4.Value = tt; _etor.ContextCascadeUpdate(_var_tt, _numst4.Source);
|
||||
int index = 0, iter = 0;
|
||||
while (index < actlist.Count) {
|
||||
if (iter++ >= 16) throw new JudgePropagationException();
|
||||
_hitop.Clear();
|
||||
int cstack = actlist[index].Definition.stack;
|
||||
int hitIndex = -1;
|
||||
while (index >= 0 && index < actlist.Count) {
|
||||
var ev = actlist[index];
|
||||
if (ev.Definition.stack != cstack) break;
|
||||
UpdateContextJudgeEvent(ev);
|
||||
var def = ev.Definition;
|
||||
if (def.hit != null) {
|
||||
_etor.Evaluate(_hitop, def.hit);
|
||||
if (_hitop.LastHit) {
|
||||
hitIndex = index;
|
||||
ev.JudgeResult = _hitop.JudgeResult;
|
||||
}
|
||||
}
|
||||
else if (hitIndex == -1) hitIndex = index;
|
||||
index++;
|
||||
}
|
||||
if (hitIndex != -1) {
|
||||
var hitEvent = actlist[hitIndex];
|
||||
UpdateContextJudgeEvent(hitEvent);
|
||||
var def = hitEvent.Definition;
|
||||
if (def == _judgePause) _sys.TogglePause();
|
||||
if (def.persist != null) _etor.Evaluate(_flagop, def.persist);
|
||||
else _flag = false;
|
||||
Execute(hitEvent, (ft + tt) / 2, def.on_hit, false);
|
||||
if (!_flag) {
|
||||
actlist.RemoveAt(hitIndex);
|
||||
--index;
|
||||
}
|
||||
if (def.prop != 0 && actlist.Count > 0) {
|
||||
index = BinarySearchFirst(actlist, def.stack - def.prop);
|
||||
if (index < 0) index = ~index;
|
||||
}
|
||||
}
|
||||
}
|
||||
_etor.ContextState = null;
|
||||
_etor.ContextEvent = null;
|
||||
}
|
||||
}
|
||||
public void Cleanup(Identifier target, float tt) {
|
||||
lock (_etor) {
|
||||
Forward(target, tt);
|
||||
var actlist = activeEvs[target];
|
||||
for (int i = actlist.Count - 1; i >= 0; i--) {
|
||||
JudgeEvent ev = actlist[i];
|
||||
if (tt > ev.EndClip) {
|
||||
actlist.RemoveAt(i);
|
||||
Execute(ev, tt, ev.Definition.on_miss, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void Forward(Identifier target, float tt) {
|
||||
var list = evs[target];
|
||||
var actlist = activeEvs[target];
|
||||
JudgeEvent ev;
|
||||
while (list.Count > 0 && (ev = list[0]).StartClip <= tt) {
|
||||
list.RemoveAt(0);
|
||||
var index = BinarySearch(actlist, (float)ev.StartClip, ev.Definition.stack);
|
||||
if (index < 0) index = ~index;
|
||||
actlist.Insert(index, ev);
|
||||
}
|
||||
}
|
||||
void Execute(JudgeEvent ev, float time, PairList<JudgeAction, PdtExpression> actions, bool onMiss, int depth = 0, int index = 0) {
|
||||
JudgeResult judgeResult = ev.JudgeResult;
|
||||
if (!onMiss && judgeResult.Time != null) {
|
||||
_jnumst.Value = judgeResult.Time.Value; _etor.ContextCascadeUpdate(_var_jt, _jnumst.Source);
|
||||
_jdnumst.Value = (float)(judgeResult.Time.Value - ev.StartTime); _etor.ContextCascadeUpdate(_var_jdt, _jdnumst.Source);
|
||||
_jvecst.Value = judgeResult.Vector; _etor.ContextCascadeUpdate(_var_jv, _jvecst.Source);
|
||||
}
|
||||
if (actions != null) {
|
||||
// Ensure that all actions that modifies judge result sources break the execution
|
||||
for (int i = index; i < actions.Count; i++) {
|
||||
var a = actions[i];
|
||||
if (a.Key.Execute(this, ev, time, a.Value, onMiss, depth, i).BreakExecution) break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var call = ev.CallContext;
|
||||
if (call.ReturnEvent != null) {
|
||||
// TODO
|
||||
if (onMiss)
|
||||
Execute(call.ReturnEvent, time, call.ReturnEvent.Definition.on_miss, true, depth + 1, 0);
|
||||
else
|
||||
Execute(call.ReturnEvent, time, call.ReturnEvent.Definition.on_hit, false, depth + 1, call.ReturnIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
void IJudge.Call(JudgeEvent ev, float time, Identifier id, bool onMiss, int index) {
|
||||
InsertEvent(ev.BaseEvent, new Clip((float)ev.StartTime, (float)ev.EndTime), id, ev.Handler, new JudgeCallContext {
|
||||
CalledOnMiss = onMiss,
|
||||
CallTime = time,
|
||||
ReturnEvent = ev,
|
||||
ReturnIndex = index + 1,
|
||||
}); // TODO optimize GC
|
||||
}
|
||||
bool IJudge.Pass(JudgeEvent ev, float time, Identifier[] ids, bool onMiss, int depth) {
|
||||
if (depth >= 16) throw new JudgePropagationException();
|
||||
foreach (var i in ids) {
|
||||
var def = _rs.judges[i];
|
||||
bool hitFlag;
|
||||
if (def.hit != null) {
|
||||
_hitop.Clear();
|
||||
_etor.Evaluate(_hitop, def.hit);
|
||||
hitFlag = _hitop.LastHit;
|
||||
}
|
||||
else hitFlag = true;
|
||||
if (hitFlag) {
|
||||
Execute(ev, time, def.on_hit, onMiss, depth + 1);
|
||||
ev.Handler.ReportJudge(ev, time, i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void IJudge.UpdateScore(ScoreOperation op, PdtExpression exp) {
|
||||
_etor.ContextSelfValue = scoreSrcs[op.name.Key];
|
||||
_etor.Evaluate(scoreOps[op.name.Key], exp);
|
||||
InvalidateScore(op.name.Key);
|
||||
foreach (var s in _rs.scores) {
|
||||
if (s.Value.value != null) {
|
||||
_etor.ContextSelfValue = scoreSrcs[s.Key.Key];
|
||||
_etor.Evaluate(scoreOps[s.Key.Key], s.Value.value);
|
||||
InvalidateScore(s.Key.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region Score
|
||||
readonly IntKeyedDictionary<int> scoreStringKeys = new IntKeyedDictionary<int>();
|
||||
readonly IntKeyedDictionary<int> scoreStringKeysRev = new IntKeyedDictionary<int>();
|
||||
readonly IntKeyedDictionary<PropSrc> scoreSrcs = new IntKeyedDictionary<PropSrc>();
|
||||
readonly IntKeyedDictionary<PropOp> scoreOps = new IntKeyedDictionary<PropOp>();
|
||||
readonly IntKeyedDictionary<ScoreDefinition> scoreDefs = new IntKeyedDictionary<ScoreDefinition>();
|
||||
readonly IntKeyedDictionary<float> scores = new IntKeyedDictionary<float>();
|
||||
readonly IntKeyedDictionary<string> scoreStringCache = new IntKeyedDictionary<string>();
|
||||
readonly ArrayPool<byte> scoreStringPool = new ArrayPool<byte>();
|
||||
readonly IntKeyedDictionary<string> scoreFormatCache = new IntKeyedDictionary<string>();
|
||||
readonly TargetString scoreFullStr = new TargetString();
|
||||
readonly StringBuffer scoreFullBuf = new StringBuffer();
|
||||
void InitScores() {
|
||||
foreach (var s in _rs.scores) {
|
||||
var key = s.Key.Key;
|
||||
var strkey = IdentifierManager.Shared.Request("_score_" + (string)s.Key.Name);
|
||||
scoreStringKeys.Add(key, strkey);
|
||||
scoreStringKeysRev.Add(strkey, key);
|
||||
scoreSrcs.Add(key, new PropSrc.Float(() => scores[key]));
|
||||
scoreOps.Add(key, new PropOp.Float(v => scores[key] = v));
|
||||
scoreDefs.Add(key, s.Value);
|
||||
scores.Add(key, s.Value.init);
|
||||
scoreStringCache.Add(scoreStringKeys[key], null);
|
||||
scoreSrcs.Add(scoreStringKeys[key], new ScoreStringSrc(scoreStringPool, () => scores[key], scoreDefs[key].format));
|
||||
scoreFormatCache[key] = string.Format("{{0:{0}}}", s.Value.format);
|
||||
}
|
||||
}
|
||||
void InvalidateScore(int key) {
|
||||
scoreSrcs[key].Invalidate();
|
||||
scoreStringCache[scoreStringKeys[key]] = null;
|
||||
scoreSrcs[scoreStringKeys[key]].Invalidate();
|
||||
}
|
||||
public bool TryGetScoreSrc(int key, out PropSrc value) {
|
||||
return scoreSrcs.TryGetValue(key, out value);
|
||||
}
|
||||
public TargetString GetFullFormattedScoreString() {
|
||||
lock (_etor) {
|
||||
bool flag = false;
|
||||
scoreFullBuf.Clear();
|
||||
foreach (var s in scores) {
|
||||
var id = s.Key;
|
||||
scoreFullBuf.AppendFormat(flag ? "\n{0}: " : "{0}: ", (string)IdentifierManager.Shared.Retrieve(id));
|
||||
scoreFullBuf.AppendFormat(scoreFormatCache[id], scores[id]);
|
||||
flag = true;
|
||||
}
|
||||
scoreFullStr.Length = scoreFullBuf.Count;
|
||||
var arr = scoreFullStr.TrustedAsArray();
|
||||
scoreFullBuf.CopyTo(0, arr, 0, scoreFullBuf.Count);
|
||||
return scoreFullStr;
|
||||
}
|
||||
}
|
||||
class ScoreStringSrc : PropSrc {
|
||||
readonly Func<float> _cb;
|
||||
readonly string _format;
|
||||
readonly ArrayPool<byte> _pool;
|
||||
readonly StringBuffer _buf = new StringBuffer() { Culture = CultureInfo.InvariantCulture };
|
||||
public ScoreStringSrc(ArrayPool<byte> pool, Func<float> cb, string format)
|
||||
: base(PdtInternalType.String) {
|
||||
_pool = pool;
|
||||
_cb = cb;
|
||||
_format = string.Format("{{0:{0}}}", format);
|
||||
}
|
||||
public override void Invalidate() {
|
||||
if (buf != null) {
|
||||
_pool.Return(buf);
|
||||
base.Invalidate();
|
||||
}
|
||||
}
|
||||
protected override unsafe void InternalGet() {
|
||||
var src = _cb();
|
||||
_buf.Clear();
|
||||
_buf.AppendFormat(_format, src);
|
||||
int strlen = _buf.Count;
|
||||
buf = _pool.Rent(sizeof(int) + strlen * sizeof(char));
|
||||
fixed (byte* _ptr = buf) {
|
||||
*(int*)_ptr = strlen;
|
||||
char* ptr = (char*)(_ptr + sizeof(int));
|
||||
_buf.CopyTo(ptr, 0, strlen);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class JudgePropagationException : Exception {
|
||||
public JudgePropagationException() : base("Judge propagation limit reached\nThe ruleset has invalid judge definitions") { }
|
||||
public JudgePropagationException(string message) : base(message) { }
|
||||
public JudgePropagationException(string message, Exception inner) : base(message, inner) { }
|
||||
protected JudgePropagationException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
}
|
11
Assets/Cryville/Crtr/Ruleset/Judge.cs.meta
Normal file
11
Assets/Cryville/Crtr/Ruleset/Judge.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3063149610959f4e853ced6341a9d36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
81
Assets/Cryville/Crtr/Ruleset/JudgeAction.cs
Normal file
81
Assets/Cryville/Crtr/Ruleset/JudgeAction.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Cryville.Common;
|
||||
using Cryville.Common.Pdt;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Cryville.Crtr.Ruleset {
|
||||
internal struct JudgeActionResult {
|
||||
public bool BreakExecution;
|
||||
public bool PreventRecycle;
|
||||
}
|
||||
public abstract class JudgeAction {
|
||||
public static JudgeAction Construct(HashSet<string> a, string k) {
|
||||
if (a.Remove("pass")) {
|
||||
return new Pass(a, from i in k.Split(',') select new Identifier(i.Trim()));
|
||||
}
|
||||
else if (a.Remove("call")) {
|
||||
return new Call(a, new Identifier(k));
|
||||
}
|
||||
else if (a.Remove("score")) {
|
||||
return new Score(a, k);
|
||||
}
|
||||
else if (a.Remove("var")) {
|
||||
return new Variable(a, new Identifier(k));
|
||||
}
|
||||
throw new FormatException("Invalid judge action format.");
|
||||
}
|
||||
public readonly HashSet<string> annotations;
|
||||
public JudgeAction(IEnumerable<string> a) {
|
||||
annotations = a.ToHashSet();
|
||||
}
|
||||
public virtual void Optimize(PdtEvaluatorBase etor, PdtExpression value) { etor.Optimize(value); }
|
||||
internal abstract JudgeActionResult Execute(IJudge judge, JudgeEvent ev, float time, PdtExpression exp, bool onMiss, int depth, int index);
|
||||
public class Call : JudgeAction {
|
||||
readonly Identifier _target;
|
||||
public Call(IEnumerable<string> a, Identifier k) : base(a) {
|
||||
_target = k;
|
||||
}
|
||||
internal override JudgeActionResult Execute(IJudge judge, JudgeEvent ev, float time, PdtExpression exp, bool onMiss, int depth, int index) {
|
||||
judge.Call(ev, time, _target, onMiss, index);
|
||||
return new JudgeActionResult { BreakExecution = true, PreventRecycle = true };
|
||||
}
|
||||
}
|
||||
public class Pass : JudgeAction {
|
||||
readonly Identifier[] _targets;
|
||||
public Pass(IEnumerable<string> a, IEnumerable<Identifier> k) : base(a) {
|
||||
_targets = k.ToArray();
|
||||
}
|
||||
internal override JudgeActionResult Execute(IJudge judge, JudgeEvent ev, float time, PdtExpression exp, bool onMiss, int depth, int index) {
|
||||
return new JudgeActionResult { BreakExecution = judge.Pass(ev, time, _targets, onMiss, depth) };
|
||||
}
|
||||
}
|
||||
public class Score : JudgeAction {
|
||||
readonly ScoreOperation _op;
|
||||
public Score(IEnumerable<string> a, string k) : base(a) {
|
||||
_op = new ScoreOperation(k);
|
||||
}
|
||||
public Score(ScoreOperation op) : base(Enumerable.Empty<string>()) {
|
||||
_op = op;
|
||||
}
|
||||
public override void Optimize(PdtEvaluatorBase etor, PdtExpression value) {
|
||||
base.Optimize(etor, value);
|
||||
if (_op.op != default(Identifier)) PdtExpression.PatchCompound(_op.name.Key, _op.op.Key, value);
|
||||
}
|
||||
internal override JudgeActionResult Execute(IJudge judge, JudgeEvent ev, float time, PdtExpression exp, bool onMiss, int depth, int index) {
|
||||
judge.UpdateScore(_op, exp);
|
||||
return new JudgeActionResult();
|
||||
}
|
||||
}
|
||||
public class Variable : JudgeAction {
|
||||
readonly Identifier _target;
|
||||
public Variable(IEnumerable<string> a, Identifier k) : base(a) {
|
||||
_target = k;
|
||||
}
|
||||
internal override JudgeActionResult Execute(IJudge judge, JudgeEvent ev, float time, PdtExpression exp, bool onMiss, int depth, int index) {
|
||||
// throw new NotImplementedException();
|
||||
return new JudgeActionResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Cryville/Crtr/Ruleset/JudgeAction.cs.meta
Normal file
11
Assets/Cryville/Crtr/Ruleset/JudgeAction.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52c6416297266354999ce5ff46a568dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
216
Assets/Cryville/Crtr/Ruleset/RulesetDefinition.cs
Normal file
216
Assets/Cryville/Crtr/Ruleset/RulesetDefinition.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using Cryville.Common;
|
||||
using Cryville.Common.Collections.Generic;
|
||||
using Cryville.Common.Pdt;
|
||||
using Cryville.Crtr.Extension;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Cryville.Crtr.Ruleset {
|
||||
public class RulesetDefinition : MetaInfo {
|
||||
public const long CURRENT_FORMAT = 2;
|
||||
|
||||
[JsonRequired]
|
||||
public long format;
|
||||
|
||||
public string @base;
|
||||
|
||||
[JsonIgnore]
|
||||
public PdtRuleset Root { get; private set; }
|
||||
|
||||
public void LoadPdt(DirectoryInfo dir) {
|
||||
using (StreamReader pdtreader = new StreamReader(dir.FullName + "/" + data + ".pdt", Encoding.UTF8)) {
|
||||
var src = pdtreader.ReadToEnd();
|
||||
Root = (PdtRuleset)new RulesetInterpreter(src, null).Interpret(typeof(PdtRuleset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Binder(typeof(PdtBinder))]
|
||||
public class PdtRuleset {
|
||||
public Dictionary<Identifier, ConfigDefinition> configs;
|
||||
public Dictionary<Identifier, MotionDefinition> motions;
|
||||
public Dictionary<Identifier, InputDefinition> inputs;
|
||||
public Dictionary<Identifier, PdtExpression> areas;
|
||||
public Dictionary<Identifier, JudgeDefinition> judges;
|
||||
public Dictionary<Identifier, ScoreDefinition> scores;
|
||||
public Constraint constraints;
|
||||
public void Optimize(PdtEvaluatorBase etor) {
|
||||
foreach (var i in inputs) {
|
||||
var input = i.Value;
|
||||
if (input.pass != null) foreach (var e in input.pass) {
|
||||
etor.Optimize(e.Value);
|
||||
}
|
||||
}
|
||||
if (areas != null) foreach (var a in areas) {
|
||||
etor.Optimize(a.Value);
|
||||
}
|
||||
foreach (var j in judges) {
|
||||
var judge = j.Value;
|
||||
if (judge.hit != null) etor.Optimize(judge.hit);
|
||||
if (judge.on_hit != null) OptimizeJudgeActions(judge.on_hit, etor);
|
||||
if (judge.on_miss != null) OptimizeJudgeActions(judge.on_miss, etor);
|
||||
}
|
||||
foreach (var s in scores) {
|
||||
var score = s.Value;
|
||||
if (score.value != null) etor.Optimize(score.value);
|
||||
}
|
||||
constraints.Optimize(etor);
|
||||
}
|
||||
void OptimizeJudgeActions(PairList<JudgeAction, PdtExpression> actions, PdtEvaluatorBase etor) {
|
||||
foreach (var a in actions) a.Key.Optimize(etor, a.Value);
|
||||
}
|
||||
public void PrePatch(Chart chart) {
|
||||
constraints.PrePatch(chart);
|
||||
}
|
||||
}
|
||||
public class ConfigDefinition {
|
||||
public string category;
|
||||
public ConfigType type;
|
||||
public PdtExpression @default;
|
||||
public PdtExpression range;
|
||||
public PdtExpression value;
|
||||
}
|
||||
public enum ConfigType {
|
||||
unknown, number, number_stepped,
|
||||
}
|
||||
public class MotionDefinition {
|
||||
// TODO
|
||||
}
|
||||
public class InputDefinition {
|
||||
public int dim;
|
||||
public string pdim;
|
||||
public bool notnull;
|
||||
public PairList<Identifier, PdtExpression> pass;
|
||||
}
|
||||
public class JudgeDefinition {
|
||||
public int stack;
|
||||
public int prop;
|
||||
public PdtExpression clip;
|
||||
public PdtExpression input;
|
||||
public PdtExpression hit;
|
||||
public PdtExpression persist;
|
||||
public PairList<JudgeAction, PdtExpression> on_hit;
|
||||
public PairList<JudgeAction, PdtExpression> on_miss;
|
||||
#pragma warning disable IDE1006
|
||||
public PairList<ScoreOperation, PdtExpression> scores {
|
||||
set {
|
||||
if (on_hit == null) on_hit = new PairList<JudgeAction, PdtExpression>();
|
||||
int i = 0;
|
||||
foreach (var s in value) {
|
||||
on_hit.Insert(i++, new JudgeAction.Score(s.Key), s.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public Identifier[] pass {
|
||||
set {
|
||||
if (on_hit == null) on_hit = new PairList<JudgeAction, PdtExpression>();
|
||||
on_hit.Add(new JudgeAction.Pass(Enumerable.Empty<string>(), value), PdtExpression.Empty);
|
||||
}
|
||||
}
|
||||
public Identifier[] miss {
|
||||
set {
|
||||
if (on_miss == null) on_miss = new PairList<JudgeAction, PdtExpression>();
|
||||
on_miss.Add(new JudgeAction.Pass(Enumerable.Empty<string>(), value), PdtExpression.Empty);
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE1006
|
||||
}
|
||||
public class ScoreOperation {
|
||||
public Identifier name;
|
||||
public Identifier op;
|
||||
public ScoreOperation(Identifier name, Identifier op) {
|
||||
this.name = name;
|
||||
this.op = op;
|
||||
}
|
||||
public ScoreOperation(string str) {
|
||||
var m = Regex.Match(str, @"^(\S+)\s*?(\S+)?$");
|
||||
name = new Identifier(m.Groups[1].Value);
|
||||
if (!m.Groups[2].Success) return;
|
||||
op = new Identifier(m.Groups[2].Value);
|
||||
}
|
||||
public override string ToString() {
|
||||
if (op == default(Identifier)) return name.ToString();
|
||||
else return string.Format("{0} {1}", name, op);
|
||||
}
|
||||
}
|
||||
public class ScoreDefinition {
|
||||
public PdtExpression value;
|
||||
public float init = 0;
|
||||
public string format = "";
|
||||
}
|
||||
public class Constraint {
|
||||
static readonly PropOp.Arbitrary _arbop = new PropOp.Arbitrary();
|
||||
[ElementList]
|
||||
public PairList<RulesetSelectors, Constraint> Elements = new PairList<RulesetSelectors, Constraint>();
|
||||
[PropertyList]
|
||||
public PairList<ConstraintKey, PdtExpression> Properties = new PairList<ConstraintKey, PdtExpression>();
|
||||
public void Optimize(PdtEvaluatorBase etor) {
|
||||
foreach (var e in Properties) {
|
||||
etor.Optimize(e.Value);
|
||||
}
|
||||
foreach (var e in Elements) {
|
||||
e.Key.Optimize(etor);
|
||||
e.Value.Optimize(etor);
|
||||
}
|
||||
}
|
||||
public void PrePatch(ChartEvent ev) {
|
||||
var etor = PdtEvaluator.Instance;
|
||||
PropSrc src;
|
||||
etor.ContextCascadeInsert();
|
||||
etor.ContextEvent = ev;
|
||||
foreach (var prop in Properties) {
|
||||
var name = prop.Key.Name;
|
||||
switch (prop.Key.Type) {
|
||||
case ConstraintType.Property:
|
||||
if (ev.PropSrcs.TryGetValue(name, out src))
|
||||
etor.ContextSelfValue = src;
|
||||
etor.Evaluate(ev.PropOps[name], prop.Value);
|
||||
etor.ContextSelfValue = null;
|
||||
break;
|
||||
case ConstraintType.Variable:
|
||||
_arbop.Name = name;
|
||||
etor.Evaluate(_arbop, prop.Value);
|
||||
break;
|
||||
default: throw new NotSupportedException("Unknown property key type");
|
||||
}
|
||||
}
|
||||
etor.ContextEvent = null;
|
||||
foreach (var el in Elements) {
|
||||
var targets = el.Key.Match(ev);
|
||||
if (targets == null) continue;
|
||||
foreach (var target in targets)
|
||||
el.Value.PrePatch(target);
|
||||
}
|
||||
etor.ContextCascadeDiscard();
|
||||
}
|
||||
}
|
||||
public class ConstraintKey {
|
||||
public ConstraintType Type { get; private set; }
|
||||
public int Name { get; private set; }
|
||||
public ConstraintKey(ConstraintType type, string name) {
|
||||
Type = type;
|
||||
Name = IdentifierManager.Shared.Request(name);
|
||||
}
|
||||
public override string ToString() {
|
||||
switch (Type) {
|
||||
case ConstraintType.Property: return (string)IdentifierManager.Shared.Retrieve(Name);
|
||||
case ConstraintType.Variable: return string.Format("@var {0}", IdentifierManager.Shared.Retrieve(Name));
|
||||
default: return string.Format("<{0}> {1}", Type, IdentifierManager.Shared.Retrieve(Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
public enum ConstraintType {
|
||||
Property,
|
||||
Variable,
|
||||
}
|
||||
public class RulesetViolationException : Exception {
|
||||
public RulesetViolationException() { }
|
||||
public RulesetViolationException(string message) : base(message) { }
|
||||
public RulesetViolationException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
11
Assets/Cryville/Crtr/Ruleset/RulesetDefinition.cs.meta
Normal file
11
Assets/Cryville/Crtr/Ruleset/RulesetDefinition.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d7a277a6e9217e4591c39d1a220cbcf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
78
Assets/Cryville/Crtr/Ruleset/RulesetInterpreter.cs
Normal file
78
Assets/Cryville/Crtr/Ruleset/RulesetInterpreter.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Cryville.Common.Collections.Generic;
|
||||
using Cryville.Common.Pdt;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Cryville.Crtr.Ruleset {
|
||||
internal class RulesetInterpreter : PdtInterpreter {
|
||||
public RulesetInterpreter(string src, Binder binder) : base(src, binder) { }
|
||||
|
||||
readonly List<RulesetSelector> s = new List<RulesetSelector>();
|
||||
readonly HashSet<string> a = new HashSet<string>();
|
||||
protected override object InterpretKey(Type type) {
|
||||
if (PairCollection<JudgeAction, PdtExpression>.IsPairCollection(type))
|
||||
return InterpretJudgeAction();
|
||||
else if (type == typeof(Constraint))
|
||||
return InterpretConstraintKey();
|
||||
else
|
||||
return base.InterpretKey(type);
|
||||
}
|
||||
object InterpretJudgeAction() {
|
||||
a.Clear();
|
||||
while (true) {
|
||||
int pp = Position;
|
||||
switch (cc) {
|
||||
case '@':
|
||||
GetChar();
|
||||
a.Add(GetIdentifier());
|
||||
break;
|
||||
default:
|
||||
return JudgeAction.Construct(a, (string)base.InterpretKey(null));
|
||||
}
|
||||
ws();
|
||||
if (Position == pp) throw new FormatException("Invalid judge action format.");
|
||||
}
|
||||
}
|
||||
object InterpretConstraintKey() {
|
||||
s.Clear(); a.Clear();
|
||||
string key = "";
|
||||
while (true) {
|
||||
int pp = Position;
|
||||
switch (cc) {
|
||||
case '@':
|
||||
GetChar();
|
||||
a.Add(GetIdentifier());
|
||||
break;
|
||||
case '$':
|
||||
GetChar();
|
||||
s.Add(new RulesetSelector.CreateItem());
|
||||
key = null;
|
||||
break;
|
||||
case '#':
|
||||
GetChar();
|
||||
s.Add(new RulesetSelector.Index());
|
||||
break;
|
||||
case '>':
|
||||
GetChar();
|
||||
s.Add(new RulesetSelector.Property(GetExp()));
|
||||
break;
|
||||
case ';':
|
||||
case ':':
|
||||
return new ConstraintKey(a.Contains("var") ? ConstraintType.Variable : ConstraintType.Property, key);
|
||||
case '{':
|
||||
return new RulesetSelectors(s);
|
||||
case '}':
|
||||
return null;
|
||||
default:
|
||||
var p4 = GetIdentifier();
|
||||
s.Add(new RulesetSelector.EventType(p4));
|
||||
if (key != null) key += p4;
|
||||
break;
|
||||
}
|
||||
ws();
|
||||
if (Position == pp) throw new FormatException("Invalid selector or key format.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Cryville/Crtr/Ruleset/RulesetInterpreter.cs.meta
Normal file
11
Assets/Cryville/Crtr/Ruleset/RulesetInterpreter.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78904c737c51e254ab55b6686d964837
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
102
Assets/Cryville/Crtr/Ruleset/RulesetSelectors.cs
Normal file
102
Assets/Cryville/Crtr/Ruleset/RulesetSelectors.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Cryville.Common.Pdt;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Cryville.Crtr.Ruleset {
|
||||
public class RulesetSelectors {
|
||||
readonly RulesetSelector[] selectors;
|
||||
public RulesetSelectors(IEnumerable<RulesetSelector> s) {
|
||||
selectors = s.ToArray();
|
||||
}
|
||||
public void Optimize(PdtEvaluatorBase etor) {
|
||||
for (int i = 0; i < selectors.Length; i++) {
|
||||
selectors[i].Optimize(etor);
|
||||
}
|
||||
}
|
||||
public IEnumerable<ChartEvent> Match(ChartEvent ev) {
|
||||
IEnumerable<ChartEvent> result = new ChartEvent[] { ev };
|
||||
foreach (var sel in selectors) {
|
||||
IEnumerable<ChartEvent> temp = new ChartEvent[0];
|
||||
foreach (var e in result) {
|
||||
var m = sel.Match(e);
|
||||
if (m != null) temp = temp.Concat(m);
|
||||
}
|
||||
result = temp;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public override string ToString() {
|
||||
if (selectors.Length == 0) return "";
|
||||
bool flag = false;
|
||||
string r = "";
|
||||
foreach (var s in selectors) {
|
||||
if (flag) r += " " + s.ToString();
|
||||
else { r += s.ToString(); flag = true; }
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class RulesetSelector {
|
||||
public virtual void Optimize(PdtEvaluatorBase etor) { }
|
||||
public abstract IEnumerable<ChartEvent> Match(ChartEvent ev);
|
||||
public class CreateItem : RulesetSelector {
|
||||
public override string ToString() {
|
||||
return "$";
|
||||
}
|
||||
public override IEnumerable<ChartEvent> Match(ChartEvent ev) {
|
||||
if (!(ev is EventList)) throw new ArgumentException("Event is not event list");
|
||||
var tev = (EventList)ev;
|
||||
var result = tev.Create();
|
||||
tev.Events.Add(result); // TODO create at
|
||||
return new ChartEvent[] { result };
|
||||
}
|
||||
}
|
||||
public class EventType : RulesetSelector {
|
||||
readonly string _type;
|
||||
public EventType(string type) { _type = type; }
|
||||
public override string ToString() {
|
||||
return _type;
|
||||
}
|
||||
public override IEnumerable<ChartEvent> Match(ChartEvent ev) {
|
||||
if (!(ev is EventContainer)) throw new ArgumentException("Event is not container");
|
||||
var tev = (EventContainer)ev;
|
||||
return new ChartEvent[] { tev.GetEventsOfType(_type) };
|
||||
}
|
||||
}
|
||||
public class Index : RulesetSelector {
|
||||
public override string ToString() {
|
||||
return "#";
|
||||
}
|
||||
public override IEnumerable<ChartEvent> Match(ChartEvent ev) {
|
||||
if (!(ev is EventList)) throw new ArgumentException("Event is not event list");
|
||||
var tev = (EventList)ev;
|
||||
return tev.Events; // TODO select at
|
||||
}
|
||||
}
|
||||
public class Property : RulesetSelector {
|
||||
readonly PdtExpression _exp;
|
||||
readonly PdtOperator _op;
|
||||
bool _flag;
|
||||
public Property(PdtExpression exp) {
|
||||
_exp = exp;
|
||||
_op = new PropOp.Boolean(v => _flag = v);
|
||||
}
|
||||
public override string ToString() {
|
||||
return string.Format("> {{{0}}}", _exp);
|
||||
}
|
||||
public override void Optimize(PdtEvaluatorBase etor) {
|
||||
etor.Optimize(_exp);
|
||||
}
|
||||
public override IEnumerable<ChartEvent> Match(ChartEvent ev) {
|
||||
PdtEvaluator.Instance.ContextEvent = ev;
|
||||
if (!PdtEvaluator.Instance.Evaluate(_op, _exp))
|
||||
throw new EvaluationFailureException();
|
||||
PdtEvaluator.Instance.ContextEvent = null;
|
||||
if (_flag) return new ChartEvent[] { ev };
|
||||
else return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Cryville/Crtr/Ruleset/RulesetSelectors.cs.meta
Normal file
11
Assets/Cryville/Crtr/Ruleset/RulesetSelectors.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 770e66e317b9b3848a16736b68414b43
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user