using System.Collections.Generic; using System.Globalization; using System.Text; using UnityEngine; namespace ServiceSim { /// /// ロボット1台ずつの位置と状態を、**店の中の時間で5秒おきに** 残す /// (2026-10-27・エイイチの指示)。出力は robot_track.csv。 /// /// なぜ要るか /// 10/27 の夜の 1回(③2台・すれ違い入・種0)が 720 分終わらなかった。 /// ロボットが皿を持ったまま 600 秒の打ち切りを 56 回くり返したのだが、 /// **そのときロボットがどこで何をしていたかが記録に無かった**。 /// stops.csv は「止まった」ときだけ、trips.csv は往復ごとなので、 /// 「ずっと同じ所に居た」ことが後から言えなかった。 /// /// なぜ 5 秒おき・店の中の時間なのか /// ・1コマ(0.05 秒)ごとに書くと、書く重さで Mac の忙しさが変わり、 /// 数字そのものが動いてしまう(busycheck で、忙しさが /// stuck_count・robot_share を動かすと分かっている)。 /// ・刻みを **ops.NowS(店の中の時間)** で決めるので、 /// 実時間が速くても遅くても同じ行が出る(取り違えない)。 /// ・5 秒おきなら 1回(店の中 200 分)で 2 台 × 2400 = 4800 行ほど。 /// 上限の 720 分でも 17,280 行で、軽い。 /// /// det_frames.csv(1コマごと・位置の合計)との違い /// det_frames は切り分けの試し用で、ロボット位置を **合計値** でしか /// 持たないので1台ずつ追えない。こちらは常に書き、1台ずつ追える。 /// public class RobotTrackLogger { public struct Row { public float tS; public int robotId; public float x, z; public string phase; // いまの仕事(卓へ / 受け渡し口へ / 立ち往生 など) public bool passBy; // すれ違いの走り方で動いている public bool backingOff; // すれ違いをあきらめて後ずさりしている public bool narrow; // すれ違う幅が足りないと見た public int tableId; // 向かっている卓(無ければ -1) } readonly List _rows = new List(); public int RowCount { get { return _rows.Count; } } public IReadOnlyList Rows { get { return _rows; } } public void Clear() { _rows.Clear(); } /// いまの仕事を日本語で(trips.csv などと言葉をそろえる) public static string PhaseName(ServingRobotController.Phase p) { switch (p) { case ServingRobotController.Phase.Idle: return "待機"; case ServingRobotController.Phase.ToTable: return "卓へ"; case ServingRobotController.Phase.Dwell: return "卓で止まる"; case ServingRobotController.Phase.ToPass: return "受け渡し口へ"; case ServingRobotController.Phase.StuckWait: return "立ち往生"; case ServingRobotController.Phase.Done: return "往復おわり"; } return "分からない"; } /// /// その時刻のロボットを1台ずつ1行に。 /// null の枠は飛ばす(ロボット0台の条件では1行も出ない) /// public void Sample(float tS, IList robots) { if (robots == null) return; for (int i = 0; i < robots.Count; i++) { ServingRobotController r = robots[i]; if (r == null) continue; Vector3 p = r.transform.position; Row row = new Row(); row.tS = tS; row.robotId = r.robotId; row.x = p.x; row.z = p.z; row.phase = PhaseName(r.CurrentPhase); row.passBy = r.PassByMoving; row.backingOff = r.PassByBackingOff; row.narrow = r.PassByNarrow; row.tableId = r.CurrentPhase == ServingRobotController.Phase.Idle ? -1 : r.TableId; _rows.Add(row); } } public const string Header = "condition_id,seed,t_s,robot_id,x,z,phase,pass_by,backing_off,narrow,table_id"; public void Write(string path, string conditionId, int seed) { StringBuilder sb = new StringBuilder(); sb.Append(Header).Append('\n'); for (int i = 0; i < _rows.Count; i++) { Row r = _rows[i]; sb.Append(conditionId).Append(',').Append(seed.ToString(CultureInfo.InvariantCulture)).Append(',') .Append(F(r.tS)).Append(',') .Append(r.robotId.ToString(CultureInfo.InvariantCulture)).Append(',') .Append(F(r.x)).Append(',').Append(F(r.z)).Append(',') .Append(r.phase).Append(',') .Append(r.passBy ? 1 : 0).Append(',') .Append(r.backingOff ? 1 : 0).Append(',') .Append(r.narrow ? 1 : 0).Append(',') .Append(r.tableId.ToString(CultureInfo.InvariantCulture)).Append('\n'); } System.IO.File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false)); } static string F(float v) { return v.ToString("0.####", CultureInfo.InvariantCulture); } } }