Files
crtr/Assets/Cryville/Crtr/Judge.cs
2023-05-20 16:31:23 +08:00

364 lines
14 KiB
C#

using Cryville.Common;
using Cryville.Common.Buffers;
using Cryville.Common.Collections.Generic;
using Cryville.Common.Collections.Specialized;
using Cryville.Common.Pdt;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.Serialization;
using System.Text.Formatting;
using UnsafeIL;
namespace Cryville.Crtr {
public struct 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 interface IJudge {
bool Pass(JudgeEvent ev, float time, Identifier[] ids, 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.judges.Areas;
_numsrc1 = new PropSrc.Float(() => _numbuf1);
_numsrc2 = new PropSrc.Float(() => _numbuf2);
_numsrc3 = new PropSrc.Float(() => _numbuf3);
_numsrc4 = new PropSrc.Float(() => _numbuf4);
_rs.judges.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();
}
public void Prepare(StampedEvent sev, NoteHandler handler) {
var tev = (Chart.Judge)sev.Unstamped;
if (tev.Id.Key == _var_pause) throw new InvalidOperationException("Cannot assign the special judge \"pause\" to notes");
Identifier input = default(Identifier);
Clip clip = default(Clip);
var def = _rs.judges.Judges[tev.Id];
_etor.Evaluate(new PropOp.Identifier(v => input = new Identifier(v)), def.input);
_etor.Evaluate(new PropOp.Clip(v => clip = v), def.clip);
double st = sev.Time, et = st + sev.Duration;
var list = evs[input];
var ev = new JudgeEvent {
StartTime = st,
EndTime = et,
StartClip = st + clip.Behind,
EndClip = et + clip.Ahead,
BaseEvent = tev,
Definition = def,
Handler = handler,
};
var index = list.BinarySearch(ev, _stcmp);
if (index < 0) index = ~index;
list.Insert(index, ev);
}
#endregion
#region Judge
internal readonly IntKeyedDictionary<int> judgeMap = new IntKeyedDictionary<int>();
void InitJudges() {
foreach (var i in _rs.judges.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 HitOp() : base(MAX_SORTS) { }
protected override unsafe void Execute() {
LastHit = false;
for (int i = 0; i < LoadedOperandCount; i++) {
var sort = GetOperand(i).AsNumber();
if (sort <= 0) return;
if (!LastHit && sort < _sorts[i]) return;
if (sort > _sorts[i]) LastHit = true;
_buf[i] = sort;
}
if (!LastHit) return;
Array.Clear(_buf, LoadedOperandCount, MAX_SORTS - LoadedOperandCount);
fixed (float* ptrsrc = _buf, ptrdest = _sorts) {
Unsafe.CopyBlock(ptrdest, ptrsrc, MAX_SORTS * sizeof(float));
}
}
public void Clear() { Array.Clear(_sorts, 0, MAX_SORTS); }
}
static readonly int _var_fn = IdentifierManager.Shared.Request("judge_clip_from");
static readonly int _var_tn = IdentifierManager.Shared.Request("judge_clip_to");
static readonly int _var_ft = IdentifierManager.Shared.Request("input_time_from");
static readonly int _var_tt = IdentifierManager.Shared.Request("input_time_to");
float _numbuf1, _numbuf2, _numbuf3, _numbuf4;
readonly PropSrc _numsrc1, _numsrc2, _numsrc3, _numsrc4;
unsafe void LoadNum(byte[] buffer, float value) {
fixed (byte* ptr = buffer) *(float*)ptr = value;
}
// 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;
}
public void Feed(Identifier target, float ft, float tt) {
Forward(target, tt);
var actlist = activeEvs[target];
if (actlist.Count > 0) {
_numbuf3 = ft; _numsrc3.Invalidate(); _etor.ContextCascadeUpdate(_var_ft, _numsrc3);
_numbuf4 = tt; _numsrc4.Invalidate(); _etor.ContextCascadeUpdate(_var_tt, _numsrc4);
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;
_numbuf1 = (float)ev.StartTime; _numsrc1.Invalidate(); _etor.ContextCascadeUpdate(_var_fn, _numsrc1);
_numbuf2 = (float)ev.EndTime; _numsrc2.Invalidate(); _etor.ContextCascadeUpdate(_var_tn, _numsrc2);
var def = ev.Definition;
if (def.hit != null) {
_etor.Evaluate(_hitop, def.hit);
if (_hitop.LastHit) hitIndex = index;
}
else if (hitIndex == -1) hitIndex = index;
index++;
}
if (hitIndex != -1) {
var hitEvent = actlist[hitIndex];
_numbuf1 = (float)hitEvent.StartTime; _numsrc1.Invalidate(); _etor.ContextCascadeUpdate(_var_fn, _numsrc1);
_numbuf2 = (float)hitEvent.EndTime; _numsrc2.Invalidate(); _etor.ContextCascadeUpdate(_var_tn, _numsrc2);
var def = hitEvent.Definition;
if (def == _judgePause) _sys.TogglePause();
if (def.on_hit != null) Execute(hitEvent, (ft + tt) / 2, def.on_hit);
if (def.persist != null) _etor.Evaluate(_flagop, def.persist);
else _flag = 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;
}
}
}
}
}
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);
if (ev.Definition.on_miss != null) Execute(ev, tt, ev.Definition.on_miss);
}
}
}
}
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, int depth = 0) {
foreach (var a in actions) {
if (a.Key.Execute(this, ev, time, a.Value, depth)) break;
}
}
bool IJudge.Pass(JudgeEvent ev, float time, Identifier[] ids, int depth) {
if (depth >= 16) throw new JudgePropagationException();
foreach (var i in ids) {
var def = _rs.judges.Judges[i];
if (def.hit != null) _etor.Evaluate(_flagop, def.hit);
else _flag = true;
if (_flag) {
if (def.on_hit != null) Execute(ev, time, def.on_hit, 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) { }
}
}