You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
298 lines
9.6 KiB
C#
298 lines
9.6 KiB
C#
4 weeks ago
|
namespace Fusion.Editor {
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using UnityEditor;
|
||
|
using UnityEditor.IMGUI.Controls;
|
||
|
using UnityEngine;
|
||
|
using Object = UnityEngine.Object;
|
||
|
|
||
|
public class NetworkPrefabsInspector : EditorWindow {
|
||
|
|
||
|
private Grid _grid = new Grid();
|
||
|
|
||
|
[MenuItem("Tools/Fusion/Windows/Network Prefabs Inspector")]
|
||
|
[MenuItem("Window/Fusion/Network Prefabs Inspector")]
|
||
|
public static void ShowWindow() {
|
||
|
var window = GetWindow<NetworkPrefabsInspector>(false, "Network Prefabs Inspector");
|
||
|
window.Show();
|
||
|
}
|
||
|
|
||
|
private void OnEnable() {
|
||
|
_grid.PrefabTable = NetworkProjectConfig.Global.PrefabTable;
|
||
|
_grid.OnEnable();
|
||
|
}
|
||
|
|
||
|
private void OnInspectorUpdate() {
|
||
|
_grid.OnInspectorUpdate();
|
||
|
}
|
||
|
|
||
|
private void OnGUI() {
|
||
|
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar)) {
|
||
|
_grid.DrawToolbarReloadButton();
|
||
|
_grid.DrawToolbarSyncSelectionButton();
|
||
|
GUILayout.FlexibleSpace();
|
||
|
|
||
|
EditorGUI.BeginChangeCheck();
|
||
|
_grid.OnlyLoaded = GUILayout.Toggle(_grid.OnlyLoaded, "Loaded Only", EditorStyles.toolbarButton);
|
||
|
if (EditorGUI.EndChangeCheck()) {
|
||
|
_grid.ResetTree();
|
||
|
}
|
||
|
|
||
|
_grid.DrawToolbarSearchField();
|
||
|
}
|
||
|
|
||
|
var rect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
|
||
|
_grid.OnGUI(rect);
|
||
|
}
|
||
|
|
||
|
|
||
|
private enum LoadState {
|
||
|
NotLoaded,
|
||
|
Loading,
|
||
|
LoadedNoInstances,
|
||
|
Loaded
|
||
|
}
|
||
|
|
||
|
[Serializable]
|
||
|
private class InspectorTreeViewState : TreeViewState {
|
||
|
public MultiColumnHeaderState HeaderState;
|
||
|
public bool SyncSelection;
|
||
|
}
|
||
|
|
||
|
private class GridItem : FusionGridItem {
|
||
|
private readonly NetworkPrefabId _prefabId;
|
||
|
private readonly NetworkPrefabTable _prefabTable;
|
||
|
|
||
|
public GridItem(NetworkPrefabTable prefabTable, NetworkPrefabId prefabId) {
|
||
|
_prefabId = prefabId;
|
||
|
_prefabTable = prefabTable;
|
||
|
}
|
||
|
|
||
|
public int InstanceCount => _prefabTable.GetInstancesCount(_prefabId);
|
||
|
|
||
|
public string Path => AssetDatabase.GUIDToAssetPath(Guid);
|
||
|
|
||
|
public string Guid => Source?.AssetGuid.ToUnityGuidString() ?? "Null";
|
||
|
|
||
|
public override Object TargetObject {
|
||
|
get {
|
||
|
if (Source?.AssetGuid.IsValid == true) {
|
||
|
if (NetworkProjectConfigUtilities.TryGetPrefabEditorInstance(Source.AssetGuid, out var result)) {
|
||
|
return result.gameObject;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public INetworkPrefabSource Source => _prefabTable.GetSource(_prefabId);
|
||
|
|
||
|
public string Description {
|
||
|
get => Source?.Description ?? "Null";
|
||
|
}
|
||
|
|
||
|
public LoadState LoadState {
|
||
|
get {
|
||
|
if (!_prefabTable.IsAcquired(_prefabId)) {
|
||
|
return LoadState.NotLoaded;
|
||
|
}
|
||
|
|
||
|
if (!_prefabTable.GetSource(_prefabId).IsCompleted) {
|
||
|
return LoadState.Loading;
|
||
|
}
|
||
|
|
||
|
if (_prefabTable.GetInstancesCount(_prefabId) == 0) {
|
||
|
return LoadState.LoadedNoInstances;
|
||
|
}
|
||
|
|
||
|
return LoadState.Loaded;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public NetworkPrefabId PrefabId => _prefabId;
|
||
|
}
|
||
|
|
||
|
[Serializable]
|
||
|
class Grid : FusionGrid<GridItem> {
|
||
|
|
||
|
[SerializeField]
|
||
|
public NetworkPrefabTable PrefabTable;
|
||
|
[SerializeField]
|
||
|
public bool OnlyLoaded;
|
||
|
|
||
|
public override int GetContentHash() {
|
||
|
return PrefabTable?.Version ?? 0;
|
||
|
}
|
||
|
|
||
|
protected override IEnumerable<Column> CreateColumns() {
|
||
|
yield return new() {
|
||
|
headerContent = new GUIContent("State"),
|
||
|
width = 40,
|
||
|
autoResize = false,
|
||
|
cellGUI = (item, rect, _, _) => {
|
||
|
var icon = FusionEditorSkin.LoadStateIcon;
|
||
|
string label = "";
|
||
|
Color color;
|
||
|
switch (item.LoadState) {
|
||
|
case LoadState.Loaded:
|
||
|
color = Color.green;
|
||
|
label = item.InstanceCount.ToString();
|
||
|
break;
|
||
|
case LoadState.LoadedNoInstances:
|
||
|
color = Color.yellow;
|
||
|
label = "0";
|
||
|
break;
|
||
|
case LoadState.Loading:
|
||
|
color = Color.yellow;
|
||
|
color.a = 0.5f;
|
||
|
label = "0";
|
||
|
break;
|
||
|
default:
|
||
|
color = Color.gray;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
using (new FusionEditorGUI.ContentColorScope(color)) {
|
||
|
EditorGUI.LabelField(rect, new GUIContent(label, icon, item.LoadState.ToString()));
|
||
|
}
|
||
|
},
|
||
|
getComparer = order => (a, b) => {
|
||
|
var result = a.LoadState.CompareTo(b.LoadState) * order;
|
||
|
if (result != 0) {
|
||
|
return result;
|
||
|
}
|
||
|
return a.InstanceCount.CompareTo(b.InstanceCount) * order;
|
||
|
},
|
||
|
};
|
||
|
yield return new() {
|
||
|
headerContent = new GUIContent("Type"),
|
||
|
width = 40,
|
||
|
maxWidth = 40,
|
||
|
minWidth = 40,
|
||
|
cellGUI = (item, rect, _, _) => INetworkPrefabSourceDrawer.DrawThumbnail(rect, item.Source),
|
||
|
getComparer = order => (a, b) => EditorUtility.NaturalCompare(a.Source?.GetType().Name ?? "", b.Source?.GetType().Name ?? "") * order,
|
||
|
};
|
||
|
yield return MakeSimpleColumn(x => x.PrefabId, new() {
|
||
|
cellGUI = (item, rect, selected, focused) => TreeView.DefaultGUI.Label(rect, item.PrefabId.ToString(false, false), selected , focused),
|
||
|
width = 50,
|
||
|
autoResize = false
|
||
|
});
|
||
|
yield return MakeSimpleColumn(x => x.Path, new() {
|
||
|
initiallySorted = true,
|
||
|
});
|
||
|
yield return MakeSimpleColumn(x => x.Guid, new() {
|
||
|
initiallyVisible = false
|
||
|
});
|
||
|
yield return MakeSimpleColumn(x => x.Description, new() {
|
||
|
initiallyVisible = false
|
||
|
});
|
||
|
}
|
||
|
|
||
|
protected override IEnumerable<GridItem> CreateRows() {
|
||
|
if (PrefabTable == null) {
|
||
|
yield break;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < PrefabTable.Prefabs.Count; ++i) {
|
||
|
var prefabId = NetworkPrefabId.FromIndex(i);
|
||
|
if (OnlyLoaded && !PrefabTable.IsAcquired(prefabId)) {
|
||
|
continue;
|
||
|
}
|
||
|
yield return new GridItem(PrefabTable, NetworkPrefabId.FromIndex(i)) { id = (int)(i + 1) };
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override GenericMenu CreateContextMenu(GridItem item, TreeView treeView) {
|
||
|
|
||
|
var menu = new GenericMenu();
|
||
|
|
||
|
var selection = treeView.GetSelection()
|
||
|
.Select(x => NetworkPrefabId.FromIndex(x-1))
|
||
|
.ToList();
|
||
|
|
||
|
var anyLoaded = selection.Any(x => PrefabTable.IsAcquired(x));
|
||
|
var anyNotLoaded = selection.Any(x => !PrefabTable.IsAcquired(x));
|
||
|
var anyInstances = selection.Any(x => PrefabTable.GetInstancesCount(x) > 0);
|
||
|
var spawnerRunners = NetworkRunner.Instances.Where(x => x && x.IsRunning && x.CanSpawn).ToArray();
|
||
|
|
||
|
var loadContent = new GUIContent("Load");
|
||
|
var loadAsyncContent = new GUIContent("Load (async)");
|
||
|
var unloadContent = new GUIContent("Unload");
|
||
|
var selectInstancesContent = new GUIContent("Select Instances");
|
||
|
var spawnContent = new GUIContent("Spawn");
|
||
|
var spawnAsyncContent = new GUIContent("Spawn (async)");
|
||
|
|
||
|
if (anyNotLoaded) {
|
||
|
menu.AddItem(loadContent, false, () => {
|
||
|
foreach (var id in selection) {
|
||
|
PrefabTable.Load(id, isSynchronous: true);
|
||
|
}
|
||
|
});
|
||
|
menu.AddItem(loadAsyncContent, false, () => {
|
||
|
foreach (var id in selection) {
|
||
|
PrefabTable.Load(id, isSynchronous: false);
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
menu.AddDisabledItem(loadContent);
|
||
|
menu.AddDisabledItem(loadAsyncContent);
|
||
|
}
|
||
|
|
||
|
if (anyLoaded) {
|
||
|
menu.AddItem(unloadContent, false, () => {
|
||
|
foreach (var id in selection) {
|
||
|
PrefabTable.Unload(id);
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
menu.AddDisabledItem(unloadContent);
|
||
|
}
|
||
|
|
||
|
if (anyInstances) {
|
||
|
menu.AddItem(selectInstancesContent, false, () => {
|
||
|
var lookup = new HashSet<NetworkObjectTypeId>(selection.Select(x => NetworkObjectTypeId.FromPrefabId(x)));
|
||
|
Selection.objects = FindObjectsByType<NetworkObject>(FindObjectsInactive.Include, FindObjectsSortMode.None)
|
||
|
.Where(x => x.NetworkTypeId.IsValid && lookup.Contains(x.NetworkTypeId))
|
||
|
.Select(x => x.gameObject)
|
||
|
.ToArray();
|
||
|
});
|
||
|
} else {
|
||
|
menu.AddDisabledItem(selectInstancesContent);
|
||
|
}
|
||
|
|
||
|
menu.AddSeparator("");
|
||
|
|
||
|
if (spawnerRunners.Any()) {
|
||
|
if (spawnerRunners.Length > 1) {
|
||
|
foreach (var runner in spawnerRunners.Where(x => x.CanSpawn)) {
|
||
|
AddSpawnItems($"/{runner.name}", runner);
|
||
|
}
|
||
|
} else {
|
||
|
AddSpawnItems($"", spawnerRunners[0]);
|
||
|
}
|
||
|
} else {
|
||
|
menu.AddDisabledItem(spawnContent);
|
||
|
menu.AddDisabledItem(spawnAsyncContent);
|
||
|
}
|
||
|
|
||
|
void AddSpawnItems(string s, NetworkRunner networkRunner) {
|
||
|
menu.AddItem(new GUIContent($"{spawnContent.text}{s}"), false, () => {
|
||
|
foreach (var id in selection) {
|
||
|
networkRunner.TrySpawn(id, out _);
|
||
|
}
|
||
|
});
|
||
|
menu.AddItem(new GUIContent($"{spawnAsyncContent.text}{s}"), false, () => {
|
||
|
foreach (var id in selection) {
|
||
|
networkRunner.SpawnAsync(id);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return menu;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|