311 lines
11 KiB
C#
311 lines
11 KiB
C#
using Cryville.Common;
|
|
using Cryville.Common.Buffers;
|
|
using Cryville.Common.Pdt;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text.Formatting;
|
|
|
|
namespace Cryville.Crtr {
|
|
public class Judge {
|
|
#region Data
|
|
readonly PdtEvaluator _etor;
|
|
readonly PdtRuleset _rs;
|
|
readonly Dictionary<Identifier, float> ct
|
|
= new Dictionary<Identifier, float>();
|
|
readonly Dictionary<Identifier, List<JudgeEvent>> evs
|
|
= new Dictionary<Identifier, List<JudgeEvent>>();
|
|
readonly Dictionary<Identifier, List<JudgeEvent>> activeEvs
|
|
= new Dictionary<Identifier, List<JudgeEvent>>();
|
|
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; }
|
|
}
|
|
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(PdtRuleset rs) {
|
|
_etor = ChartPlayer.etor;
|
|
_rs = rs;
|
|
InitJudges();
|
|
InitScores();
|
|
}
|
|
public void Prepare(StampedEvent sev, NoteHandler handler) {
|
|
var tev = (Chart.Judge)sev.Unstamped;
|
|
Identifier input = default(Identifier);
|
|
ChartPlayer.etor.Evaluate(new PropOp.Identifier(v => input = new Identifier(v)), _rs.judges[tev.Id].input);
|
|
double st = sev.Time, et = st + sev.Duration;
|
|
List<JudgeEvent> list;
|
|
if (!evs.TryGetValue(input, out list)) {
|
|
ct.Add(input, 0);
|
|
evs.Add(input, list = new List<JudgeEvent>());
|
|
activeEvs.Add(input, new List<JudgeEvent>());
|
|
}
|
|
var def = _rs.judges[tev.Id];
|
|
var ev = new JudgeEvent {
|
|
StartTime = st,
|
|
EndTime = et,
|
|
StartClip = st + def.clip.Behind,
|
|
EndClip = et + def.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 Dictionary<int, int> judgeMap = new Dictionary<int, int>();
|
|
void InitJudges() {
|
|
foreach (var i in _rs.judges.Keys) {
|
|
judgeMap.Add(i.Key, IdentifierManager.SharedInstance.Request("judge_" + i.Name));
|
|
}
|
|
}
|
|
static bool _flag;
|
|
static readonly PropOp.Boolean _flagop = new PropOp.Boolean(v => _flag = v);
|
|
static readonly int _var_fn = IdentifierManager.SharedInstance.Request("fn");
|
|
static readonly int _var_tn = IdentifierManager.SharedInstance.Request("tn");
|
|
static readonly int _var_ft = IdentifierManager.SharedInstance.Request("ft");
|
|
static readonly int _var_tt = IdentifierManager.SharedInstance.Request("tt");
|
|
readonly byte[] _numbuf1 = new byte[sizeof(float)];
|
|
readonly byte[] _numbuf2 = new byte[sizeof(float)];
|
|
readonly byte[] _numbuf3 = new byte[sizeof(float)];
|
|
readonly byte[] _numbuf4 = new byte[sizeof(float)];
|
|
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, float time, int stack) {
|
|
if (list[0].Definition.stack == stack && list[0].StartClip == time) 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) num4 = list[num3].StartClip.CompareTo(time);
|
|
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) {
|
|
LoadNum(_numbuf3, ft); _etor.ContextCascadeUpdate(_var_ft, new PropSrc.Arbitrary(PdtInternalType.Number, _numbuf3));
|
|
LoadNum(_numbuf4, tt); _etor.ContextCascadeUpdate(_var_tt, new PropSrc.Arbitrary(PdtInternalType.Number, _numbuf4));
|
|
var index = 0;
|
|
while (index >= 0 && index < actlist.Count) {
|
|
var ev = actlist[index];
|
|
LoadNum(_numbuf1, (float)ev.StartTime); _etor.ContextCascadeUpdate(_var_fn, new PropSrc.Arbitrary(PdtInternalType.Number, _numbuf1));
|
|
LoadNum(_numbuf2, (float)ev.EndTime); _etor.ContextCascadeUpdate(_var_tn, new PropSrc.Arbitrary(PdtInternalType.Number, _numbuf2));
|
|
var def = ev.Definition;
|
|
if (def.hit != null) _etor.Evaluate(_flagop, def.hit);
|
|
else _flag = true;
|
|
if (_flag) {
|
|
if (def.scores != null) UpdateScore(def.scores);
|
|
if (def.pass != null) Pass(ev, (ft + tt) / 2, def.pass);
|
|
actlist.RemoveAt(index);
|
|
if (def.prop != 0 && actlist.Count > 0) {
|
|
index = BinarySearchFirst(actlist, (float)ev.StartClip, def.stack - def.prop);
|
|
if (index < 0) index = ~index;
|
|
}
|
|
else index++;
|
|
}
|
|
else index++;
|
|
}
|
|
}
|
|
}
|
|
bool Pass(JudgeEvent ev, float time, Identifier[] ids) {
|
|
foreach (var i in ids) {
|
|
var def = _rs.judges[i];
|
|
if (def.hit != null) _etor.Evaluate(_flagop, def.hit);
|
|
else _flag = true;
|
|
if (_flag) {
|
|
if (def.scores != null) UpdateScore(def.scores);
|
|
if (def.pass != null) Pass(ev, time, def.pass);
|
|
ev.Handler.ReportJudge(ev, time, i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
public void Cleanup(Identifier target, float tt) {
|
|
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.miss != null) Pass(ev, tt, ev.Definition.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 UpdateScore(Dictionary<ScoreOperation, PdtExpression> scoreops) {
|
|
foreach (var scoreop in scoreops) {
|
|
var key = scoreop.Key;
|
|
_etor.ContextSelfValue = scoreSrcs[key.name.Key];
|
|
_etor.Evaluate(scoreOps[key.name.Key], scoreop.Value);
|
|
InvalidateScore(key.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 Dictionary<int, int> scoreStringKeys = new Dictionary<int, int>();
|
|
readonly Dictionary<int, int> scoreStringKeysRev = new Dictionary<int, int>();
|
|
readonly Dictionary<int, PropSrc> scoreSrcs = new Dictionary<int, PropSrc>();
|
|
readonly Dictionary<int, PropOp> scoreOps = new Dictionary<int, PropOp>();
|
|
readonly Dictionary<int, ScoreDefinition> scoreDefs = new Dictionary<int, ScoreDefinition>();
|
|
readonly Dictionary<int, float> scores = new Dictionary<int, float>();
|
|
readonly Dictionary<int, string> scoreStringCache = new Dictionary<int, string>();
|
|
readonly Dictionary<int, PropSrc> scoreStringSrcs = new Dictionary<int, PropSrc>();
|
|
readonly ArrayPool<byte> scoreStringPool = new ArrayPool<byte>();
|
|
void InitScores() {
|
|
foreach (var s in _rs.scores) {
|
|
var key = s.Key.Key;
|
|
var strkey = IdentifierManager.SharedInstance.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);
|
|
scoreStringSrcs.Add(scoreStringKeys[key], new ScoreStringSrc(scoreStringPool, () => scores[key], scoreDefs[key].format));
|
|
}
|
|
}
|
|
void InvalidateScore(int key) {
|
|
scoreSrcs[key].Invalidate();
|
|
scoreStringCache[scoreStringKeys[key]] = null;
|
|
scoreStringSrcs[scoreStringKeys[key]].Invalidate();
|
|
}
|
|
string GetScoreString(int key) {
|
|
var result = scoreStringCache[key];
|
|
if (result == null) {
|
|
var rkey = scoreStringKeysRev[key];
|
|
return scoreStringCache[key] = scores[rkey].ToString(scoreDefs[rkey].format, CultureInfo.InvariantCulture);
|
|
}
|
|
else return result;
|
|
}
|
|
public bool TryGetScoreSrc(int key, out PropSrc value) {
|
|
return scoreSrcs.TryGetValue(key, out value);
|
|
}
|
|
public bool TryGetScoreStringSrc(int key, out PropSrc value) {
|
|
return scoreStringSrcs.TryGetValue(key, out value);
|
|
}
|
|
public string GetFullFormattedScoreString() {
|
|
bool flag = false;
|
|
string result = "";
|
|
foreach (var s in scores.Keys) {
|
|
result += string.Format(flag ? "\n{0}: {1}" : "{0}: {1}", IdentifierManager.SharedInstance.Retrieve(s), GetScoreString(scoreStringKeys[s]));
|
|
flag = true;
|
|
}
|
|
return result;
|
|
}
|
|
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
|
|
}
|
|
#region Definitions
|
|
public class InputDefinition {
|
|
public int dim;
|
|
public bool notnull;
|
|
public Dictionary<Identifier, PdtExpression> pass;
|
|
}
|
|
public class JudgeDefinition {
|
|
public Clip clip;
|
|
public PdtExpression input;
|
|
public PdtExpression hit;
|
|
public Identifier[] pass;
|
|
public Identifier[] miss;
|
|
public Dictionary<ScoreOperation, PdtExpression> scores;
|
|
public int stack;
|
|
public int prop = 1;
|
|
}
|
|
public class ScoreOperation {
|
|
public Identifier name;
|
|
public Identifier op;
|
|
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 = "";
|
|
}
|
|
#endregion
|
|
}
|