using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace ServiceSim.EditorTools
{
///
/// 1条件だけ回すメニュー(v2 の確かめ用)。
/// 条件と数字は ExperimentPlan の .asset。回し方だけここで決める。
///
public static class ServiceSimStage2
{
[MenuItem("ServiceSim/Run One/1条件を回す(1コマ固定 0.05 秒)", false, 40)]
public static void RunMeasure() { Run(0f, "v3", false, 0f); }
[MenuItem("ServiceSim/Run One/1条件を回す(確かめ用 0.02 秒)", false, 41)]
public static void RunReference() { Run(-1f, "v3ref", false, 0f); }
// C1:停止点だけを変えて2回回す。ほかはすべて ExperimentPlan の trial… の値。
// Inspector を触らずにメニューから選べるようにして、出力フォルダ名に停止点を入れる
// (…_off000 と …_off015)ので、2回目で1回目が上書きされない。
// 選んだ値はその回の Job にだけ使う。StoreLayout の .asset は書き換えない
// (ExperimentRunner が回し終わりにもとの値へ戻す)。
[MenuItem("ServiceSim/Run One/停止点 0 で回す(通路の中央)", false, 50)]
public static void RunOffset000() { Run(0f, "v3", true, 0f); }
[MenuItem("ServiceSim/Run One/停止点 0.15 で回す(卓寄り・直す前と同じ)", false, 51)]
public static void RunOffset015() { Run(0f, "v3", true, 0.15f); }
// 見る用:その回だけ字幕と描画を出し、1倍速で進める。
// 設定ファイル(ExperimentPlan・StoreLayout)は書き換えない。
// 計測の比べには使わない回なので、出力フォルダの名前に _view を付ける。
[MenuItem("ServiceSim/Run One/見る用:字幕あり・描画あり(1倍速)", false, 55)]
public static void RunView() { Run(0f, "view", false, 0f, true); }
[MenuItem("ServiceSim/Run One/出力フォルダを開く", false, 60)]
public static void OpenOutput()
{
ExperimentPlan plan = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.ExperimentPlanPath);
if (plan == null) { Debug.LogError("[ServiceSim] ExperimentPlan が見つかりません"); return; }
if (!Directory.Exists(plan.trialOutputRoot))
{
Debug.LogWarning("[ServiceSim] まだありません: " + plan.trialOutputRoot);
return;
}
EditorUtility.RevealInFinder(plan.trialOutputRoot);
}
///
/// captureOverride: 0 なら plan の計測用、-1 なら plan の確かめ用。
/// setOffset が true なら、卓の停止点を offset(m)にしてこの回だけ回す。
/// .asset は書き換えない(ExperimentRunner が回し終わりにもとの値へ戻す)。
/// false なら .asset にいま入っている値のまま回す。
/// どちらでも、出力フォルダ名には実際に使う停止点を入れる。
///
static void Run(float captureOverride, string label, bool setOffset, float offset)
{
Run(captureOverride, label, setOffset, offset, false);
}
/// view が true なら、その回だけ字幕と描画を出して1倍速で回す(見る用)
static void Run(float captureOverride, string label, bool setOffset, float offset, bool view)
{
ExperimentRunner runner;
ExperimentPlan plan;
if (!ServiceSimRunSupport.Prepare(out runner, out plan)) return;
StoreLayout layout = CurrentLayout();
if (layout == null)
{
Debug.LogError("[ServiceSim] StoreLayout が見つかりません。ServiceSim/Setup を実行してください");
return;
}
float useOffset = setOffset ? offset : layout.deliveryOffsetFromAisleCenter;
float capture = captureOverride < 0f ? plan.referenceCaptureDeltaTime : 0f;
ExperimentRunner.Job job = new ExperimentRunner.Job();
job.aisleWidthIndex = plan.trialAisleWidthIndex;
job.crowd = plan.trialCrowd;
job.robots = plan.trialRobots;
job.seed = plan.trialSeed;
job.applyDeliveryOffset = setOffset;
job.deliveryOffset = offset;
job.folder = Path.Combine(plan.trialOutputRoot,
"one_" + plan.runName + "_" + label + "_w" + plan.trialAisleWidthIndex +
"_" + ExperimentPlan.CrowdName(plan.trialCrowd) +
"_r" + plan.trialRobots + "_s" + plan.trialSeed +
"_" + OffsetTag(useOffset) + (view ? "_view" : "") +
"_" + DateTime.Now.ToString("yyyyMMdd_HHmm"));
// フォルダ名に回し始めた時刻を入れるので、同じ条件でも上書きにならない
if (Directory.Exists(job.folder))
Debug.LogWarning("[ServiceSim] このフォルダはすでにあります。上書きになります: " + job.folder);
runner.SetSingleJob(job);
runner.timeMode = view ? ExperimentRunner.TimeMode.RealTime
: ExperimentRunner.TimeMode.FixedStep;
runner.captureDeltaTimeOverride = view ? 0f : capture;
runner.runLabel = label;
runner.viewMode = view;
runner.runOnPlay = true;
EditorUtility.SetDirty(runner);
float stepSec = capture > 0f ? capture : plan.measureCaptureDeltaTime;
float simMin = plan.warmupMinutes + plan.measureMinutes;
Debug.Log("[ServiceSim] " + (view ? "見る用に1条件を回します" : "1条件を回します") +
"(Play に入ります)。" +
(view ? "\n 見る用:字幕あり・描画あり・1倍速。設定ファイルは書き換えていません。" +
"\n 計測の比べには使いません(フォルダ名に _view が付きます)。" +
"\n 途中で Play を止めてよいです(そのときは何も書き出しません)"
: "") +
"\n 1コマ " + (view ? "実時間(1倍速)" : stepSec.ToString("0.###") + " 秒") +
"\n 店の中 " + simMin + " 分(慣らし " + plan.warmupMinutes + " 分を含む)" +
"\n 細い通路: 番号 " + plan.trialAisleWidthIndex +
" / 混み具合: " + ExperimentPlan.CrowdName(plan.trialCrowd) +
" / 台数: " + plan.trialRobots + " / 種: " + plan.trialSeed +
"\n 停止点: " + useOffset.ToString("0.###") + " m" +
(setOffset
? "(メニューが決めた値。この回だけ使い、.asset は "
+ layout.deliveryOffsetFromAisleCenter.ToString("0.###") + " m のまま)"
: "(.asset の値)") +
"\n 置き場所: " + job.folder +
"\n おわると自動で Play を抜けます");
EditorApplication.EnterPlaymode();
}
/// フォルダ名に入れる停止点(0 → off000、0.15 → off015)
static string OffsetTag(float offsetM)
{
return "off" + Mathf.RoundToInt(offsetM * 100f).ToString("000");
}
static StoreLayout CurrentLayout()
{
StoreBuilder builder = UnityEngine.Object.FindObjectOfType();
if (builder != null && builder.layout != null) return builder.layout;
return AssetDatabase.LoadAssetAtPath(ServiceSimSetup.FamilyLayoutPath);
}
}
/// メニューから回すときの、共通の下ごしらえ
public static class ServiceSimRunSupport
{
public static bool Prepare(out ExperimentRunner runner, out ExperimentPlan plan)
{
runner = null;
plan = null;
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
Debug.LogWarning("[ServiceSim] すでに Play 中です。止めてからもう一度押してください");
return false;
}
Scene active = SceneManager.GetActiveScene();
if (active.path != ServiceSimSetup.ScenePath)
{
if (!File.Exists(ServiceSimSetup.ScenePath))
{
Debug.LogError("[ServiceSim] シーンがありません。先に ServiceSim/Setup を実行してください");
return false;
}
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return false;
EditorSceneManager.OpenScene(ServiceSimSetup.ScenePath, OpenSceneMode.Single);
}
runner = UnityEngine.Object.FindObjectOfType();
if (runner == null)
{
Debug.LogError("[ServiceSim] シーンに ExperimentRunner がありません。ServiceSim/Setup を実行してください");
return false;
}
plan = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.ExperimentPlanPath);
if (plan == null) { Debug.LogError("[ServiceSim] ExperimentPlan が見つかりません"); return false; }
if (runner.plan == null) runner.plan = plan;
if (runner.service == null)
runner.service = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.ServiceSpecPath);
if (runner.robotPrefab == null)
runner.robotPrefab = AssetDatabase.LoadAssetAtPath(
ServiceSimSetup.Root + "/Robot/ServingRobot.prefab");
if (runner.robotSpec == null)
runner.robotSpec = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.RobotSpecPath);
if (runner.simParams == null)
runner.simParams = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.SimParamsPath);
// 実時間の上限と、版の覚え書きは **段階Bだけ** が入れる。
// ここで 0・空に戻しておき、ほかのメニューの回し方を前と同じに保つ
runner.maxWallSecondsPerRun = 0f;
runner.unityCs = "";
runner.pendingNote = "";
return true;
}
/// Play を抜けたら runOnPlay を下ろす(手で Play しても回り出さないように)
[InitializeOnLoadMethod]
static void Hook()
{
EditorApplication.playModeStateChanged -= OnChanged;
EditorApplication.playModeStateChanged += OnChanged;
}
static void OnChanged(PlayModeStateChange state)
{
if (state != PlayModeStateChange.EnteredEditMode) return;
ExperimentRunner runner = UnityEngine.Object.FindObjectOfType();
if (runner == null) return;
if (!runner.runOnPlay && !runner.viewMode) return;
runner.runOnPlay = false;
runner.viewMode = false; // 見る用は、その回だけ
EditorUtility.SetDirty(runner);
}
}
}