Initial commit: AIGame Unity client with URP, HybridCLR, YooAsset and tooling.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom inspector for the <see cref="EditorPipelineManager"/> settings asset: shows live server
|
||||
/// status (accurate running check + actual port, read from the static owner) and offers
|
||||
/// Start/Stop/Restart buttons, alongside the editable configuration. Watchdog edits are pushed to
|
||||
/// a running server immediately; port/autoStart apply on the next start.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(EditorPipelineManager))]
|
||||
class EditorPipelineManagerEditor : UnityEditor.Editor
|
||||
{
|
||||
// Repaint() from inside OnInspectorGUI re-triggers OnInspectorGUI on the next editor tick, so
|
||||
// a running server redrew this IMGUI-heavy inspector every frame (several ms) while merely
|
||||
// visible. Repaint from EditorApplication.update at a low rate instead.
|
||||
private const double StatusRepaintInterval = 0.25;
|
||||
private double m_NextStatusRepaint;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.update += ThrottledStatusRepaint;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EditorApplication.update -= ThrottledStatusRepaint;
|
||||
}
|
||||
|
||||
private void ThrottledStatusRepaint()
|
||||
{
|
||||
var mgr = target as EditorPipelineManager;
|
||||
if (mgr == null || !(mgr.IsServerRunning || EditorCodeReloadWatcher.IsWatching))
|
||||
return;
|
||||
|
||||
double now = EditorApplication.timeSinceStartup;
|
||||
if (now < m_NextStatusRepaint)
|
||||
return;
|
||||
m_NextStatusRepaint = now + StatusRepaintInterval;
|
||||
Repaint();
|
||||
}
|
||||
|
||||
/// <summary>Draw the default inspector plus live server status and Start/Stop/Restart controls.</summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var mgr = (EditorPipelineManager)target;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawDefaultInspector();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
// Push edited watchdog config to the live server (owned by PipelineServerStartup), so
|
||||
// changes take effect without a restart.
|
||||
var server = PipelineServerStartup.Server;
|
||||
if (server != null)
|
||||
{
|
||||
server.WatchdogEnabled = mgr.WatchdogEnabled;
|
||||
server.WatchdogIntervalSeconds = mgr.WatchdogIntervalSeconds;
|
||||
server.LogRequestsResponses = mgr.LogRequestsResponses;
|
||||
}
|
||||
|
||||
// Eval-usage telemetry config lives on static fields (recording happens in the
|
||||
// Runtime command layer, not the server), so push edits there too — applies live.
|
||||
// Goes through the same ApplyEvalTelemetrySettings the startup/server-start paths
|
||||
// use, so there is exactly one place that knows the settings->statics mapping.
|
||||
PipelineServerStartup.ApplyEvalTelemetrySettings(mgr);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Status", EditorStyles.boldLabel);
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUILayout.Toggle("Running", mgr.IsServerRunning);
|
||||
EditorGUILayout.IntField("Actual Port", mgr.ActualPort);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(mgr.IsServerRunning))
|
||||
{
|
||||
if (GUILayout.Button("Start"))
|
||||
mgr.StartServer();
|
||||
}
|
||||
using (new EditorGUI.DisabledScope(!mgr.IsServerRunning))
|
||||
{
|
||||
if (GUILayout.Button("Stop"))
|
||||
mgr.StopServer();
|
||||
}
|
||||
if (GUILayout.Button("Restart"))
|
||||
mgr.RestartServer();
|
||||
}
|
||||
|
||||
DrawCodeReloadWatchSection();
|
||||
// Live status is kept current by ThrottledStatusRepaint — never Repaint() from here.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Watch the project's Assets folder and re-apply affected [CodeReload] methods on every save,
|
||||
/// driving <see cref="EditorCodeReloadWatcher"/>. Neither scope, target, nor backend is a
|
||||
/// choice: the whole Assets tree is watched, each save goes to the connected player(s) if any,
|
||||
/// else applies in-editor, and both paths run on the interpreter; a read-only row shows the
|
||||
/// current target resolution. Config is locked while a watch is active.
|
||||
/// </summary>
|
||||
private void DrawCodeReloadWatchSection()
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Code Reload Interpreter Watch", EditorStyles.boldLabel);
|
||||
|
||||
bool watching = EditorCodeReloadWatcher.IsWatching;
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
// Not a picker: each save is pushed to the connected player(s) if any, else applied
|
||||
// in-editor — resolved per save, so plugging in a device mid-watch reroutes the next save.
|
||||
int connected = EditorCodeReloadWatcher.ConnectedPlayerCount;
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
EditorGUILayout.LabelField(
|
||||
new GUIContent("Target", "Resolved automatically at each file change: development player(s) if connected, otherwise the reload applies in this editor process. Both run on the interpreter."),
|
||||
new GUIContent(connected > 0
|
||||
? $"Player — {connected} connected"
|
||||
: "In Editor — no player connected"));
|
||||
|
||||
// Outside the watching-locked scope: read at each player-connect event, so toggling it
|
||||
// takes effect for the next connection even while a watch is active.
|
||||
EditorGUILayout.PropertyField(
|
||||
serializedObject.FindProperty("m_CodeReloadRepushOnConnect"),
|
||||
new GUIContent("Re-push On Connect",
|
||||
"Re-push the watched code-reload state to any player that connects while the watch " +
|
||||
"is active. A restarted player boots the original build without previously pushed " +
|
||||
"overrides; this catches it up immediately instead of waiting for the next save."));
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(watching))
|
||||
{
|
||||
// The UI always watches the whole Assets tree; StartWatch itself accepts any
|
||||
// file or folder for CLI/API callers.
|
||||
if (GUILayout.Button("Start Watching"))
|
||||
EditorCodeReloadWatcher.StartWatch(Path.GetFullPath(Application.dataPath),
|
||||
isFolder: true);
|
||||
}
|
||||
using (new EditorGUI.DisabledScope(!watching))
|
||||
{
|
||||
if (GUILayout.Button("Stop Watching"))
|
||||
EditorCodeReloadWatcher.StopWatch();
|
||||
}
|
||||
}
|
||||
|
||||
if (watching)
|
||||
EditorGUILayout.HelpBox(
|
||||
"Asset auto-refresh is disabled while watching, so saved scripts code-reload instead of " +
|
||||
"triggering a domain reload. New or changed assets won't import until you refresh manually " +
|
||||
"(Cmd/Ctrl+R); Stop Watching restores your auto-refresh setting.",
|
||||
MessageType.Warning);
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
// Read WatchPath once: it goes null the instant the watch stops (domain reload / StopWatch),
|
||||
// which can race the `watching` captured at the top of this method — guard against null here.
|
||||
var watchPath = EditorCodeReloadWatcher.WatchPath;
|
||||
// The live Target row above shows where saves currently go; this line covers what is watched.
|
||||
string status = watching && !string.IsNullOrEmpty(watchPath)
|
||||
? $"{(EditorCodeReloadWatcher.IsFolder ? "folder" : "file")} · {Path.GetFileName(watchPath.TrimEnd('/', '\\'))}"
|
||||
: "idle";
|
||||
EditorGUILayout.LabelField("Watching", status);
|
||||
|
||||
if (watching)
|
||||
{
|
||||
// Liveness: saves applying while the event count stays flat means the OS file
|
||||
// watcher is dead and the reconcile poll is carrying the watch.
|
||||
EditorGUILayout.LabelField("Watcher Events",
|
||||
$"{EditorCodeReloadWatcher.FsEventCount}{Ago(EditorCodeReloadWatcher.LastFsEventUtc)}");
|
||||
EditorGUILayout.LabelField("Last Apply",
|
||||
EditorCodeReloadWatcher.LastApplyFile is string f
|
||||
? $"{f}{Ago(EditorCodeReloadWatcher.LastApplyUtc)}"
|
||||
: "none yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string Ago(System.DateTime? utc)
|
||||
{
|
||||
if (utc == null) return "";
|
||||
var s = (System.DateTime.UtcNow - utc.Value).TotalSeconds;
|
||||
return s < 1 ? " · just now"
|
||||
: s < 120 ? $" · {s:0}s ago"
|
||||
: s < 7200 ? $" · {s / 60:0}m ago"
|
||||
: $" · {s / 3600:0}h ago";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user