diff --git a/src/Templates.meta b/src/CodeTemplates.meta similarity index 100% rename from src/Templates.meta rename to src/CodeTemplates.meta diff --git a/src/Templates/Component.cs.txt b/src/CodeTemplates/Component.cs.txt similarity index 100% rename from src/Templates/Component.cs.txt rename to src/CodeTemplates/Component.cs.txt diff --git a/src/Templates/Component.cs.txt.meta b/src/CodeTemplates/Component.cs.txt.meta similarity index 100% rename from src/Templates/Component.cs.txt.meta rename to src/CodeTemplates/Component.cs.txt.meta diff --git a/src/Templates/ComponentExtended.cs.txt b/src/CodeTemplates/ComponentExtended.cs.txt similarity index 100% rename from src/Templates/ComponentExtended.cs.txt rename to src/CodeTemplates/ComponentExtended.cs.txt diff --git a/src/Templates/ComponentExtended.cs.txt.meta b/src/CodeTemplates/ComponentExtended.cs.txt.meta similarity index 100% rename from src/Templates/ComponentExtended.cs.txt.meta rename to src/CodeTemplates/ComponentExtended.cs.txt.meta diff --git a/src/Templates/Runner.cs.txt b/src/CodeTemplates/Runner.cs.txt similarity index 100% rename from src/Templates/Runner.cs.txt rename to src/CodeTemplates/Runner.cs.txt diff --git a/src/Templates/Runner.cs.txt.meta b/src/CodeTemplates/Runner.cs.txt.meta similarity index 100% rename from src/Templates/Runner.cs.txt.meta rename to src/CodeTemplates/Runner.cs.txt.meta diff --git a/src/Templates/RunnerExtended.cs.txt b/src/CodeTemplates/RunnerExtended.cs.txt similarity index 100% rename from src/Templates/RunnerExtended.cs.txt rename to src/CodeTemplates/RunnerExtended.cs.txt diff --git a/src/Templates/RunnerExtended.cs.txt.meta b/src/CodeTemplates/RunnerExtended.cs.txt.meta similarity index 100% rename from src/Templates/RunnerExtended.cs.txt.meta rename to src/CodeTemplates/RunnerExtended.cs.txt.meta diff --git a/src/Templates/Startup.cs.txt b/src/CodeTemplates/Startup.cs.txt similarity index 100% rename from src/Templates/Startup.cs.txt rename to src/CodeTemplates/Startup.cs.txt diff --git a/src/Templates/Startup.cs.txt.meta b/src/CodeTemplates/Startup.cs.txt.meta similarity index 100% rename from src/Templates/Startup.cs.txt.meta rename to src/CodeTemplates/Startup.cs.txt.meta diff --git a/src/Templates/System.cs.txt b/src/CodeTemplates/System.cs.txt similarity index 100% rename from src/Templates/System.cs.txt rename to src/CodeTemplates/System.cs.txt diff --git a/src/Templates/System.cs.txt.meta b/src/CodeTemplates/System.cs.txt.meta similarity index 100% rename from src/Templates/System.cs.txt.meta rename to src/CodeTemplates/System.cs.txt.meta diff --git a/src/Templates/SystemExtended.cs.txt b/src/CodeTemplates/SystemExtended.cs.txt similarity index 100% rename from src/Templates/SystemExtended.cs.txt rename to src/CodeTemplates/SystemExtended.cs.txt diff --git a/src/Templates/SystemExtended.cs.txt.meta b/src/CodeTemplates/SystemExtended.cs.txt.meta similarity index 100% rename from src/Templates/SystemExtended.cs.txt.meta rename to src/CodeTemplates/SystemExtended.cs.txt.meta diff --git a/src/Templates/TemplateGenerator.cs b/src/CodeTemplates/TemplateGenerator.cs similarity index 85% rename from src/Templates/TemplateGenerator.cs rename to src/CodeTemplates/TemplateGenerator.cs index d1f7a0c..4bdcee4 100644 --- a/src/Templates/TemplateGenerator.cs +++ b/src/CodeTemplates/TemplateGenerator.cs @@ -1,6 +1,7 @@ #if UNITY_EDITOR using System; using System.IO; +using System.Runtime.Versioning; using System.Text; using UnityEditor; using UnityEditor.ProjectWindowCallback; @@ -12,9 +13,9 @@ namespace DCFApixels.DragonECS.Editors { private const int MENU_ITEM_PRIORITY = -198; - private const string TITLE = "DragonECS Template Generator"; + private const string TITLE = EcsConsts.FRAMEWORK_NAME + " Template Generator"; - private const string MENU_ITEM_PATH = "Assets/Create/DragonECS/"; + private const string MENU_ITEM_PATH = "Assets/Create/" + EcsConsts.FRAMEWORK_NAME + "/"; private const string NAMESPACE_TAG = "#NAMESPACE#"; private const string SCRIPTANAME_TAG = "#SCRIPTNAME#"; @@ -29,25 +30,25 @@ namespace DCFApixels.DragonECS.Editors #endregion #region GenerateMethods - [MenuItem(MENU_ITEM_PATH + "[Template]Startup", false, MENU_ITEM_PRIORITY)] + [MenuItem(MENU_ITEM_PATH + "[CodeTemplate]Startup", false, MENU_ITEM_PRIORITY)] public static void CreateSturtupScript() => CreateScript("Startup"); - [MenuItem(MENU_ITEM_PATH + "[Template]System", false, MENU_ITEM_PRIORITY)] + [MenuItem(MENU_ITEM_PATH + "[CodeTemplate]System", false, MENU_ITEM_PRIORITY)] public static void CreateSystemSimpleScript() => CreateScript("System"); - [MenuItem(MENU_ITEM_PATH + "[Template]Component", false, MENU_ITEM_PRIORITY)] + [MenuItem(MENU_ITEM_PATH + "[CodeTemplate]Component", false, MENU_ITEM_PRIORITY)] public static void CreateComponentSimpleScript() => CreateScript("Component"); - [MenuItem(MENU_ITEM_PATH + "[Template]Runner", false, MENU_ITEM_PRIORITY)] + [MenuItem(MENU_ITEM_PATH + "[CodeTemplate]Runner", false, MENU_ITEM_PRIORITY)] public static void CreateRunnerSimpleScript() => CreateScript("Runner"); - [MenuItem(MENU_ITEM_PATH + "[Template]System Extended", false, MENU_ITEM_PRIORITY)] + [MenuItem(MENU_ITEM_PATH + "[CodeTemplate]System Extended", false, MENU_ITEM_PRIORITY)] public static void CreateSystemScript() => CreateScript("SystemExtended"); - [MenuItem(MENU_ITEM_PATH + "[Template]Component Extended", false, MENU_ITEM_PRIORITY)] + [MenuItem(MENU_ITEM_PATH + "[CodeTemplate]Component Extended", false, MENU_ITEM_PRIORITY)] public static void CreateComponentScript() => CreateScript("ComponentExtended"); - [MenuItem(MENU_ITEM_PATH + "[Template]Runner Extended", false, MENU_ITEM_PRIORITY)] + [MenuItem(MENU_ITEM_PATH + "[CodeTemplate]Runner Extended", false, MENU_ITEM_PRIORITY)] public static void CreateRunnerScript() => CreateScript("RunnerExtended"); diff --git a/src/Templates/TemplateGenerator.cs.meta b/src/CodeTemplates/TemplateGenerator.cs.meta similarity index 100% rename from src/Templates/TemplateGenerator.cs.meta rename to src/CodeTemplates/TemplateGenerator.cs.meta diff --git a/src/Debug/DebugService/UnityDebugService.cs b/src/Debug/DebugService/UnityDebugService.cs index c8ae00e..90605e1 100644 --- a/src/Debug/DebugService/UnityDebugService.cs +++ b/src/Debug/DebugService/UnityDebugService.cs @@ -15,7 +15,7 @@ namespace DCFApixels.DragonECS string log; if (!string.IsNullOrEmpty(tag)) { - log = $"[{tag}] {v}"; + log = $".[{tag}] {v}"; string taglower = tag.ToLower(); if (taglower.Contains("warning")) { diff --git a/src/Debug/Systems/DebugMonitorPrefs.cs b/src/Debug/Editor/DebugMonitorPrefs.cs similarity index 100% rename from src/Debug/Systems/DebugMonitorPrefs.cs rename to src/Debug/Editor/DebugMonitorPrefs.cs diff --git a/src/Debug/Systems/DebugMonitorPrefs.cs.meta b/src/Debug/Editor/DebugMonitorPrefs.cs.meta similarity index 100% rename from src/Debug/Systems/DebugMonitorPrefs.cs.meta rename to src/Debug/Editor/DebugMonitorPrefs.cs.meta diff --git a/src/Debug/Editor/EcsEditor.cs b/src/Debug/Editor/EcsEditor.cs index 31102a8..bf7f92e 100644 --- a/src/Debug/Editor/EcsEditor.cs +++ b/src/Debug/Editor/EcsEditor.cs @@ -1,20 +1,40 @@ #if UNITY_EDITOR +using System; +using System.Reflection; +using System.Runtime.InteropServices; using UnityEngine; namespace DCFApixels.DragonECS.Editors { public static class EcsEditor { + private static SparseArray colorBoxeStyles = new SparseArray(); public static GUIStyle GetStyle(Color color, float alphaMultiplier) { - GUIStyle style = new GUIStyle(GUI.skin.box); - Color componentColor = color; - componentColor.a *= alphaMultiplier; - style.normal.background = CreateTexture(2, 2, componentColor); + color.a *= alphaMultiplier; + return GetStyle(color); + } + public static GUIStyle GetStyle(Color32 color32) + { + int colorCode = new Color32Union(color32).colorCode; + if (colorBoxeStyles.TryGetValue(colorCode, out GUIStyle style)) + { + if (style == null) + style = CreateStyle(color32, colorCode); + return style; + } + style = CreateStyle(color32, colorCode); + colorBoxeStyles.Add(colorCode, style); return style; } - + private static GUIStyle CreateStyle(Color32 color32, int colorCode) + { + GUIStyle result = new GUIStyle(GUI.skin.box); + Color componentColor = color32; + result.normal.background = CreateTexture(2, 2, componentColor); + return result; + } private static Texture2D CreateTexture(int width, int height, Color color) { var pixels = new Color[width * height]; @@ -26,6 +46,73 @@ namespace DCFApixels.DragonECS.Editors result.Apply(); return result; } + + + public static string GetGenericName(Type type) + { + string friendlyName = type.Name; + if (type.IsGenericType) + { + int iBacktick = friendlyName.IndexOf('`'); + if (iBacktick > 0) + friendlyName = friendlyName.Remove(iBacktick); + + friendlyName += "<"; + Type[] typeParameters = type.GetGenericArguments(); + for (int i = 0; i < typeParameters.Length; ++i) + { + string typeParamName = GetGenericName(typeParameters[i]); + friendlyName += (i == 0 ? typeParamName : "," + typeParamName); + } + friendlyName += ">"; + } + return friendlyName; + } + + public static string GetName() => GetName(typeof(T)); + public static string GetName(Type type) + { + var atr = type.GetCustomAttribute(); + return atr != null ? atr.name : GetGenericName(type); + } + + public static string GetDescription() => GetDescription(typeof(T)); + public static string GetDescription(Type type) + { + var atr = type.GetCustomAttribute(); + return atr != null ? atr.description : string.Empty; + } + + #region Utils + [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 4)] + private readonly ref struct Color32Union + { + [FieldOffset(0)] + public readonly int colorCode; + [FieldOffset(0)] + public readonly byte r; + [FieldOffset(1)] + public readonly byte g; + [FieldOffset(2)] + public readonly byte b; + [FieldOffset(3)] + public readonly byte a; + public Color32Union(byte r, byte g, byte b, byte a) : this() + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + public Color32Union(Color32 color) : this() + { + r = color.r; + g = color.g; + b = color.b; + a = color.a; + } + } + #endregion } } #endif diff --git a/src/Debug/Systems/DebugModule.cs b/src/Debug/Systems/DebugModule.cs index 1353562..f64f978 100644 --- a/src/Debug/Systems/DebugModule.cs +++ b/src/Debug/Systems/DebugModule.cs @@ -2,7 +2,7 @@ { public sealed class DebugModule : IEcsModule { - public const string DEBUG_SYSTEMS_BLOCK = nameof(DEBUG_SYSTEMS_BLOCK); + public const string DEBUG_LAYER = nameof(DEBUG_LAYER); public EcsWorld[] _worlds; public DebugModule(params EcsWorld[] worlds) { @@ -11,11 +11,11 @@ void IEcsModule.ImportSystems(EcsPipeline.Builder b) { - b.InsertSystemsBlock(DEBUG_SYSTEMS_BLOCK, EcsConsts.POST_END_SYSTEMS_BLOCK); - b.Add(new PipelineDebugSystem(), DEBUG_SYSTEMS_BLOCK); + b.Layers.Insert(EcsConsts.POST_END_LAYER, DEBUG_LAYER); + b.Add(new PipelineDebugSystem(), DEBUG_LAYER); foreach (var world in _worlds) { - b.Add(new WorldDebugSystem(world), DEBUG_SYSTEMS_BLOCK); + b.Add(new WorldDebugSystem(world), DEBUG_LAYER); } } } diff --git a/src/Debug/Systems/DebugMonitorBase.cs b/src/Debug/Systems/DebugMonitorBase.cs index 1215b1b..f4f89d1 100644 --- a/src/Debug/Systems/DebugMonitorBase.cs +++ b/src/Debug/Systems/DebugMonitorBase.cs @@ -1,6 +1,4 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; +using UnityEngine; namespace DCFApixels.DragonECS.Unity.Debug diff --git a/src/Debug/Systems/PipelineDebugSystem.cs b/src/Debug/Systems/PipelineDebugSystem.cs index 77221d7..5deb86f 100644 --- a/src/Debug/Systems/PipelineDebugSystem.cs +++ b/src/Debug/Systems/PipelineDebugSystem.cs @@ -1,5 +1,4 @@ -using DCFApixels.DragonECS.Unity; -using DCFApixels.DragonECS.Unity.Debug; +using DCFApixels.DragonECS.Unity.Debug; using System.Reflection; using UnityEngine; @@ -7,7 +6,7 @@ using UnityEngine; namespace DCFApixels.DragonECS { [DebugHide, DebugColor(DebugColor.Gray)] - public class PipelineDebugSystem : IEcsPreInitSystem + public class PipelineDebugSystem : IEcsPreInitProcess { private string _monitorName; public PipelineDebugSystem(string monitorName = "Pipeline") @@ -15,13 +14,20 @@ namespace DCFApixels.DragonECS _monitorName = monitorName; } - void IEcsPreInitSystem.PreInit(EcsPipeline pipeline) + void IEcsPreInitProcess.PreInit(EcsPipeline pipeline) { PipelineDebugMonitor monitor = new GameObject(EcsConsts.DEBUG_PREFIX + _monitorName).AddComponent(); monitor.source = this; monitor.pipeline = pipeline; monitor.monitorName = _monitorName; + PipelineProcessesDebugMonitor processesMonitor = new GameObject(EcsConsts.DEBUG_PREFIX + "Processes Matrix").AddComponent(); + processesMonitor.transform.parent = monitor.transform; + processesMonitor.source = this; + processesMonitor.pipeline = pipeline; + processesMonitor.monitorName = "Processes Matrix"; + + //foreach (var item in pipeline.AllSystems) //Вырезано пока не сделаю TODO в SystemDebugMonitor //{ // DebugNameAttribute debugName = item.GetType().GetCustomAttribute(); @@ -37,11 +43,18 @@ namespace DCFApixels.DragonECS internal EcsPipeline pipeline; } + public class PipelineProcessesDebugMonitor : DebugMonitorBase + { + internal PipelineDebugSystem source; + internal EcsPipeline pipeline; + } + #if UNITY_EDITOR namespace Editors { using DCFApixels.DragonECS.RunnersCore; using System; + using System.Collections.Generic; using System.Linq; using UnityEditor; @@ -102,7 +115,7 @@ namespace DCFApixels.DragonECS private void DrawSystem(IEcsSystem system) { - if(system is SystemsBlockMarkerSystem markerSystem) + if (system is SystemsBlockMarkerSystem markerSystem) { GUILayout.EndVertical(); GUILayout.BeginVertical(EcsEditor.GetStyle(Color.black, 0.2f)); @@ -120,7 +133,7 @@ namespace DCFApixels.DragonECS if (CheckIsHidden(type)) return; - string name = type.Name; + string name = EcsEditor.GetGenericName(type); Color color = (GetAttribute(type) ?? _fakeDebugColorAttribute).GetUnityColor(); GUILayout.BeginVertical(EcsEditor.GetStyle(color, 0.2f)); @@ -140,7 +153,7 @@ namespace DCFApixels.DragonECS Color color = (GetAttribute(type) ?? _fakeDebugColorAttribute).GetUnityColor(); GUILayout.BeginVertical(EcsEditor.GetStyle(color, 0.2f)); - GUILayout.Label(type.Name, EditorStyles.boldLabel); + GUILayout.Label(EcsEditor.GetGenericName(type), EditorStyles.boldLabel); GUILayout.Label(string.Join(", ", runner.Targets.Cast().Select(o => o.GetType().Name)), systemsListStyle); GUILayout.EndVertical(); } @@ -161,7 +174,166 @@ namespace DCFApixels.DragonECS return target.GetCustomAttribute() != null; } } - } + [CustomEditor(typeof(PipelineProcessesDebugMonitor))] + public class PipelineProcessesDebugMonitorEditor : Editor + { + private bool _isInit = false; + private List _processesList = new List(); + private Dictionary _processeIndexes = new Dictionary(); + + private PipelineProcessesDebugMonitor Target => (PipelineProcessesDebugMonitor)target; + private Type systemInterfaceType = typeof(IEcsSystem); + + private IEcsSystem[] _systems; + private void Init() + { + if (_isInit) + return; + bool showHidden = DebugMonitorPrefs.instance.IsShowHidden; + _processesList.Clear(); + _processeIndexes.Clear(); + if (showHidden) + _systems = Target.pipeline.AllSystems.Where(o => o is SystemsBlockMarkerSystem == false).ToArray(); + else + _systems = Target.pipeline.AllSystems.Where(o => o.GetType().GetCustomAttribute() == null).ToArray(); + + int i = 0; + foreach (var system in _systems) + { + foreach (var intr in system.GetType().GetInterfaces()) + { + if(systemInterfaceType.IsAssignableFrom(intr) && systemInterfaceType != intr && (showHidden || intr.GetCustomAttribute() == null)) + { + ProcessData data; + if (!_processeIndexes.TryGetValue(intr, out int index)) + { + index = _processesList.Count; + _processeIndexes.Add(intr, index); + + data = new ProcessData(); + _processesList.Add(data); + + data.name = EcsEditor.GetGenericName(intr); + data.interfaceType = intr; + data.systemsBitMask = new BitMask(_systems.Length); + } + data = _processesList[index]; + data.systemsBitMask[i] = true; + } + } + i++; + } + + _isInit = true; + } + private Vector2 _position; + private Vector2 _cellsize = new Vector2(EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight); + private Vector2 _nameCellSize = new Vector2(200f, 200f); + + public override void OnInspectorGUI() + { + EditorGUI.BeginChangeCheck(); + DebugMonitorPrefs.instance.IsShowHidden = EditorGUILayout.Toggle("Show Hidden", DebugMonitorPrefs.instance.IsShowHidden); + if (EditorGUI.EndChangeCheck()) + { + _isInit = false; + } + + Init(); + + Rect rect; + Rect lineRect; + GUILayout.Label("", GUILayout.ExpandWidth(true), GUILayout.Height(400f)); + rect = GUILayoutUtility.GetLastRect(); + + rect.height = 400f; + + + Rect rectView = new Rect(0f, 0f, _nameCellSize.x + _cellsize.x * _processesList.Count, _nameCellSize.y + _cellsize.y * _systems.Length); + _position = GUI.BeginScrollView(rect, _position, rectView, true, true); + + List systeNames = new List(); + + var blackStyle = EcsEditor.GetStyle(Color.black, 0.04f); + var whiteStyle = EcsEditor.GetStyle(Color.white, 0.04f); + GUIContent label = new GUIContent(); + + + Vector2 pivod = _nameCellSize; + rect = new Rect(); + rect.y = _nameCellSize.y; + rect.width = _nameCellSize.x; + rect.height = _cellsize.x; + rect.y -= _cellsize.y; + for (int i = 0; i < _processesList.Count; i++) + { + lineRect = rect; + lineRect.y = 0f; + lineRect.x = _nameCellSize.x + _cellsize.x * i; + lineRect.width = _cellsize.x; + lineRect.height = rectView.height; + GUI.Label(lineRect, "", i % 2 == 1 ? whiteStyle : blackStyle); + + GUIUtility.RotateAroundPivot(90, pivod); + //GUIContent label = new GUIContent(_processesList[i].name, "." + _processesList[i].name); + label.text = _processesList[i].name; + label.tooltip = "." + _processesList[i].name; + GUI.Label(rect, label, EditorStyles.miniBoldLabel); + GUIUtility.RotateAroundPivot(-90, pivod); + + pivod.x += _cellsize.x; + rect.x += _cellsize.x; + } + + //GUIUtility.RotateAroundPivot(-90, _nameCellSize); + rect = new Rect(); + rect.y = _nameCellSize.y; + rect.width = _nameCellSize.x; + rect.height = _cellsize.x; + for (int i = 0; i < _systems.Length; i++) + { + string name = EcsEditor.GetGenericName(_systems[i].GetType()); + systeNames.Add(name); + + lineRect = rect; + lineRect.width = rectView.width; + GUI.Label(lineRect, "", i % 2 == 1 ? whiteStyle : blackStyle); + + // GUIContent label = new GUIContent(name, i + " " + name); + label.text = name; + label.tooltip = i + " " + name; + GUI.Label(rect, label, EditorStyles.miniBoldLabel); + rect.y += _cellsize.y; + } + + for (int x = 0; x < _processesList.Count; x++) + { + var process = _processesList[x]; + for (int y = 0; y < _systems.Length; y++) + { + string systemName = systeNames[x]; + rect = new Rect(x * _cellsize.x + _nameCellSize.x, y * _cellsize.y + _nameCellSize.y, _cellsize.x, _cellsize.y); + bool flag = process.systemsBitMask[y]; + string labeltext = flag ? "^" : " "; + label.text = labeltext; + label.tooltip = $"{process.name}-{systemName}"; + GUI.Label(rect, label); + //GUI.Label(rect, lable, flag ? whiteStyle : blackStyle); + // GUI.Label(rect, label, EditorStyles.helpBox); + } + } + + GUI.EndScrollView(); + } + + private class ProcessData + { + public Type interfaceType; + public string name; + public BitMask systemsBitMask; + } + } + } #endif } diff --git a/src/Debug/Systems/SystemDebugMonitor.cs b/src/Debug/Systems/SystemDebugMonitor.cs deleted file mode 100644 index ca4a8f1..0000000 --- a/src/Debug/Systems/SystemDebugMonitor.cs +++ /dev/null @@ -1,29 +0,0 @@ -using DCFApixels.DragonECS.Unity.Debug; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -namespace DCFApixels.DragonECS.Unity -{ - public class SystemDebugMonitor :MonoBehaviour - { - [SerializeReference] - private IEcsSystem _target; //TODO переделать подручнуюотрисовку, потому как [SerializeReference] не работает с generic-ами - - public static SystemDebugMonitor CreateMonitor(Transform parent, IEcsSystem system, string name) - { - GameObject go = new GameObject(name); - go.transform.parent = parent; - go.transform.position = Vector3.zero; - go.transform.rotation = Quaternion.identity; - - go.SetActive(false); - - SystemDebugMonitor result = go.AddComponent(); - - result._target = system; - - return result; - } - } -} diff --git a/src/Debug/Systems/WorldDebugSystem.cs b/src/Debug/Systems/WorldDebugSystem.cs index 1d50c61..6378b66 100644 --- a/src/Debug/Systems/WorldDebugSystem.cs +++ b/src/Debug/Systems/WorldDebugSystem.cs @@ -6,17 +6,18 @@ using UnityEngine; namespace DCFApixels.DragonECS { [DebugHide, DebugColor(DebugColor.Gray)] - public class WorldDebugSystem : IEcsRunSystem + public class WorldDebugSystem : IEcsRunProcess { private string _monitorName; private EcsWorld _ecsWorld; - public WorldDebugSystem(EcsWorld ecsWorld, string monitorName = "World") + public WorldDebugSystem(EcsWorld ecsWorld, string monitorName = null) { _monitorName = monitorName; + if (string.IsNullOrEmpty(_monitorName)) _monitorName = ecsWorld.GetType().Name; _ecsWorld = ecsWorld; WorldDebugMonitor monitor = new GameObject(EcsConsts.DEBUG_PREFIX + _monitorName).AddComponent(); - WorldPoolsMonitor poolsmonitor = new GameObject(EcsConsts.DEBUG_PREFIX + _monitorName).AddComponent(); + WorldPoolsMonitor poolsmonitor = new GameObject(EcsConsts.DEBUG_PREFIX + "Pools").AddComponent(); poolsmonitor.transform.SetParent(monitor.transform); monitor.source = this; @@ -49,8 +50,11 @@ namespace DCFApixels.DragonECS { private WorldDebugMonitor Target => (WorldDebugMonitor)target; - - + public override void OnInspectorGUI() + { + GUILayout.Label($"Size: {Target.world.Capacity}"); + GUILayout.Label($"Total entities: {Target.world.Count}"); + } } } #endif @@ -83,75 +87,81 @@ namespace DCFApixels.DragonECS public override void OnInspectorGUI() { - // _scroll = GUILayout.BeginScrollView(_scroll, GUILayout.Height(800f)); - // var pools = Target.world.GetAllPools().ToArray().Where(o => !(o is EcsNullPool)).OfType(); - // - // GUILayout.Label("", GUILayout.ExpandWidth(true)); - // - // float width = GUILayoutUtility.GetLastRect().width; - // - // Vector3 newPoolBlockSize = _poolBlockMinSize; - // int widthCount = Mathf.Max(1, Mathf.Min((Mathf.FloorToInt(width / _poolBlockMinSize.x)), pools.Count())); - // newPoolBlockSize.x = width / widthCount; - // - // int x = -1, y = 0; - // foreach (var pool in pools) - // { - // if(++x >= widthCount) - // { - // x = 0; - // y++; - // } - // - // DrawPoolBlock(pool, new Rect(newPoolBlockSize.x * x, newPoolBlockSize.y * y, newPoolBlockSize.x, newPoolBlockSize.y)); - // } - // GUILayout.EndScrollView(); + _scroll = GUILayout.BeginScrollView(_scroll, GUILayout.Height(800f)); + var pools = Target.world.AllPools.ToArray().Where(o => !o.IsNullOrDummy()).OfType(); + + GUILayout.Label("", GUILayout.ExpandWidth(true)); + + float width = GUILayoutUtility.GetLastRect().width; + + Vector3 newPoolBlockSize = _poolBlockMinSize; + int widthCount = Mathf.Max(1, Mathf.Min((Mathf.FloorToInt(width / _poolBlockMinSize.x)), pools.Count())); + newPoolBlockSize.x = width / widthCount; + + int x = -1, y = 0; + foreach (var pool in pools) + { + if(++x >= widthCount) + { + x = 0; + y++; + } + + DrawPoolBlock(pool, new Rect(newPoolBlockSize.x * x, newPoolBlockSize.y * y, newPoolBlockSize.x, newPoolBlockSize.y)); + } + GUILayout.EndScrollView(); } - // private void DrawPoolBlock(IEcsPool pool, Rect position) - // { - // Color defaultContentColor = GUI.contentColor; - // GUI.contentColor = Color.black * 0.925f; - // - // position = AddMargin(position, 1f, 1f); - // - // EditorGUI.DrawRect(position, Color.black* 0.16f); - // - // Rect progressBar = new Rect(Vector2.zero, _poolProgressBasrSize); - // progressBar.width = position.width; - // progressBar.center = position.center - Vector2.up * _poolBlockMinSize.y * 0.09f; - // - // - // Color mainColor = new Color(0.3f, 1f, 0f, 1f); - // var debugColor = pool.ComponentType.GetCustomAttribute(); - // if (debugColor != null) - // { - // mainColor = debugColor.GetUnityColor(); - // } - // Color backgroundColor = mainColor * 0.3f + Color.white * 0.2f; - // - // EditorGUI.DrawRect(progressBar, backgroundColor); - // - // progressBar.yMin = progressBar.yMax - ((float)pool.Count / pool.Capacity) * progressBar.height; - // - // GUIStyle textStyle0 = EditorStyles.miniBoldLabel; - // textStyle0.alignment = TextAnchor.MiddleCenter; - // - // Color foregroundColor = mainColor; - // EditorGUI.DrawRect(progressBar, foregroundColor); - // GUI.Label(progressBar, pool.Count.ToString(), textStyle0); - // - // GUIStyle textStyle1 = EditorStyles.miniBoldLabel; - // textStyle1.alignment = TextAnchor.UpperCenter; - // GUI.Label(AddMargin(position, 3f, 3f), "Total\r\n"+ pool.Capacity, textStyle1); - // - // GUI.contentColor = defaultContentColor; - // GUIStyle textStyle2 = EditorStyles.miniBoldLabel; - // textStyle2.alignment = TextAnchor.LowerCenter; - // GUI.Label(AddMargin(position, -10f, 3f), pool.ComponentType.Name, textStyle2); - // - // } + private void DrawPoolBlock(IEcsPool pool, Rect position) + { + int count = pool.Count; + int capacity = pool.Capacity < 0 ? count : pool.Capacity; + + Color defaultContentColor = GUI.contentColor; + GUI.contentColor = Color.black * 0.925f; + + position = AddMargin(position, 1f, 1f); + + EditorGUI.DrawRect(position, Color.black* 0.16f); + + Rect progressBar = new Rect(Vector2.zero, _poolProgressBasrSize); + progressBar.width = position.width; + progressBar.center = position.center - Vector2.up * _poolBlockMinSize.y * 0.09f; + + + Color mainColor = new Color(0.3f, 1f, 0f, 1f); + var debugColor = pool.ComponentType.GetCustomAttribute(); + if (debugColor != null) + { + mainColor = debugColor.GetUnityColor(); + } + Color backgroundColor = mainColor * 0.3f + Color.white * 0.2f; + + EditorGUI.DrawRect(progressBar, backgroundColor); + + progressBar.yMin = progressBar.yMax - ((float)count / capacity) * progressBar.height; + + GUIStyle textStyle0 = new GUIStyle(EditorStyles.miniBoldLabel); + textStyle0.alignment = TextAnchor.MiddleCenter; + + Color foregroundColor = mainColor; + EditorGUI.DrawRect(progressBar, foregroundColor); + GUI.Label(progressBar, count.ToString(), textStyle0); + + GUIStyle textStyle1 = new GUIStyle(EditorStyles.miniBoldLabel); + textStyle1.alignment = TextAnchor.UpperCenter; + GUI.Label(AddMargin(position, 3f, 3f), "Total\r\n"+ capacity, textStyle1); + + GUI.contentColor = defaultContentColor; + GUIStyle textStyle2 = new GUIStyle(EditorStyles.miniBoldLabel); + textStyle2.wordWrap = true; + textStyle2.alignment = TextAnchor.LowerCenter; + string name = EcsEditor.GetGenericName(pool.ComponentType); + GUIContent label = new GUIContent(name, $"t({name})"); + GUI.Label(AddMargin(position, -10f, 3f), label, textStyle2); + + } private Rect AddMargin(Rect rect, Vector2 value) { diff --git a/src/EcsUnityConsts.cs b/src/EcsUnityConsts.cs new file mode 100644 index 0000000..5196c2e --- /dev/null +++ b/src/EcsUnityConsts.cs @@ -0,0 +1,7 @@ +namespace DCFApixels.DragonECS +{ + public static class EcsUnityConsts + { + public const string INFO_MARK = "[i]"; + } +} diff --git a/src/Debug/Systems/SystemDebugMonitor.cs.meta b/src/EcsUnityConsts.cs.meta similarity index 83% rename from src/Debug/Systems/SystemDebugMonitor.cs.meta rename to src/EcsUnityConsts.cs.meta index 31cb0e4..8b01daf 100644 --- a/src/Debug/Systems/SystemDebugMonitor.cs.meta +++ b/src/EcsUnityConsts.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: bf4cdc68b5b89a14c81a53b600078536 +guid: b33e303fef8298b4db42987fe9b94e45 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/src/Runners.meta b/src/EntityTemplate.meta similarity index 77% rename from src/Runners.meta rename to src/EntityTemplate.meta index 05d544d..a326327 100644 --- a/src/Runners.meta +++ b/src/EntityTemplate.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 70c8e3cb9125ee14fad9fdee48c3e8ba +guid: 11dc6a014c6c7e84ab61979e25010c57 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/src/EntityTemplate/EntityTemplate.cs b/src/EntityTemplate/EntityTemplate.cs new file mode 100644 index 0000000..da1f441 --- /dev/null +++ b/src/EntityTemplate/EntityTemplate.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace DCFApixels.DragonECS +{ + public class EntityTemplate : MonoBehaviour, ITemplateInternal + { + [SerializeReference] + private ITemplateComponent[] _components; + string ITemplateInternal.ComponentsPropertyName => nameof(_components); + + public void Apply(EcsWorld world, int entityID) + { + foreach (var item in _components) + item.Add(world, entityID); + } + } +} diff --git a/src/EntityTemplate/EntityTemplate.cs.meta b/src/EntityTemplate/EntityTemplate.cs.meta new file mode 100644 index 0000000..3a14226 --- /dev/null +++ b/src/EntityTemplate/EntityTemplate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c96e3aedd5a69443af75096e5561265 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EntityTemplate/EntityTemplateEditor.cs b/src/EntityTemplate/EntityTemplateEditor.cs new file mode 100644 index 0000000..cb7054d --- /dev/null +++ b/src/EntityTemplate/EntityTemplateEditor.cs @@ -0,0 +1,235 @@ +using System; +using System.Reflection; + +namespace DCFApixels.DragonECS +{ +#if UNITY_EDITOR + namespace Editors + { + using UnityEditor; + using UnityEngine; + + public class EntityTemplateEditorBase: Editor + { + private static readonly Rect RemoveButtonRect = new Rect(0f, 0f, 15f, 15f); + private static readonly Rect TooltipIconRect = new Rect(0f, 0f, 15f, 15f); + + private GUIStyle removeButtonStyle; + private GenericMenu genericMenu; + private bool _isInit = false; + + #region Init + private void Init() + { + if (genericMenu == null) + _isInit = false; + if (_isInit) + return; + + var tmpstylebase = EcsEditor.GetStyle(new Color(0.9f, 0f, 0.22f), 0.5f); + var tmpStyle = EcsEditor.GetStyle(new Color(1f, 0.5f, 0.7f), 0.5f); + + removeButtonStyle = new GUIStyle(EditorStyles.linkLabel); + removeButtonStyle.alignment = TextAnchor.MiddleCenter; + + removeButtonStyle.normal = tmpstylebase.normal; + removeButtonStyle.hover = tmpStyle.normal; + removeButtonStyle.active = tmpStyle.normal; + removeButtonStyle.focused = tmpStyle.normal; + + removeButtonStyle.padding = new RectOffset(0, 0, 0, 0); + removeButtonStyle.margin = new RectOffset(0, 0, 0, 0); + removeButtonStyle.border = new RectOffset(0, 0, 0, 0); + + genericMenu = new GenericMenu(); + + var dummies = TemplateBrowsableTypeCache.Dummies; + foreach ( var dummy in dummies ) + { + string name, description; + if (dummy is ITemplateComponentName browsableName) + name = browsableName.Name; + else + name = EcsEditor.GetName(dummy.GetType()); + + if (dummy is TemplateComponentInitializerBase initializer) + description = initializer.Description; + else + description = EcsEditor.GetDescription(dummy.GetType()); + + if (!string.IsNullOrEmpty(description)) + { + name = $"{name} {EcsUnityConsts.INFO_MARK}"; + } + + genericMenu.AddItem(new GUIContent(name, description), false, OnAddComponent, dummy); + } + + _isInit = true; + } + #endregion + + #region Add/Remove + private void OnAddComponent(object obj) + { + Type componentType = obj.GetType(); + if (this.target is ITemplateInternal target) + { + SerializedProperty componentsProp = serializedObject.FindProperty(target.ComponentsPropertyName); + for (int i = 0; i < componentsProp.arraySize; i++) + { + if (componentsProp.GetArrayElementAtIndex(i).managedReferenceValue.GetType() == componentType) + return; + } + + componentsProp.InsertArrayElementAtIndex(0); + + componentsProp.GetArrayElementAtIndex(0).managedReferenceValue = ((ITemplateComponent)obj).Clone(); + + serializedObject.ApplyModifiedProperties(); + EditorUtility.SetDirty(this.target); + } + } + private void OnRemoveComponentAt(int index) + { + if (this.target is ITemplateInternal target) + { + SerializedProperty componentsProp = serializedObject.FindProperty(target.ComponentsPropertyName); + componentsProp.DeleteArrayElementAtIndex(index); + serializedObject.ApplyModifiedProperties(); + EditorUtility.SetDirty(this.target); + } + } + #endregion + + protected void Draw(ITemplateInternal target) + { + Init(); + SerializedProperty componentsProp = serializedObject.FindProperty(target.ComponentsPropertyName); + if (componentsProp == null) + return; + + DrawTop(target); + for (int i = 0; i < componentsProp.arraySize; i++) + { + DrawComponentData(componentsProp.GetArrayElementAtIndex(i), i); + GUILayout.Space(EditorGUIUtility.standardVerticalSpacing * 2); + } + } + private void DrawTop(ITemplateInternal target) + { + if (GUILayout.Button("Add Component", GUILayout.Height(24f))) + { + Init(); + genericMenu.ShowAsContext(); + } + } + private void DrawComponentData(SerializedProperty componentRefProp, int index) + { + ITemplateComponent browsable = (ITemplateComponent)componentRefProp.managedReferenceValue; + ITemplateComponentName browsableName = browsable as ITemplateComponentName; + + if (componentRefProp.managedReferenceValue == null) + { + DrawDamagedComponent(componentRefProp, index); + return; + } + + Type componentType; + SerializedProperty componentProperty = componentRefProp; + TemplateComponentInitializerBase customInitializer = componentProperty.managedReferenceValue as TemplateComponentInitializerBase; + if (customInitializer != null) + { + componentProperty = componentProperty.FindPropertyRelative("component"); + componentType = customInitializer.Type; + } + else + { + componentType = componentProperty.managedReferenceValue.GetType(); + } + + Type type = browsable.GetType(); + string name = browsableName == null ? type.Name : GetLastPathComponent(browsableName.Name); + string description = customInitializer != null ? customInitializer.Description : componentType.GetCustomAttribute()?.description; + Color panelColor = customInitializer != null ? customInitializer.Color : componentType.GetCustomAttribute()?.GetUnityColor() ?? Color.black; + + + GUILayout.BeginHorizontal(); + + GUILayout.BeginVertical(EcsEditor.GetStyle(panelColor, 0.2f)); + + EditorGUI.BeginChangeCheck(); + GUIContent label = new GUIContent(name, $"{name} "); + EditorGUILayout.PropertyField(componentProperty, label, true); + if (EditorGUI.EndChangeCheck()) + { + componentProperty.serializedObject.ApplyModifiedProperties(); + EditorUtility.SetDirty(componentProperty.serializedObject.targetObject); + } + + Rect lastrect = GUILayoutUtility.GetLastRect(); + Rect removeButtonRect = RemoveButtonRect; + removeButtonRect.center = new Vector2(lastrect.xMax + removeButtonRect.width, lastrect.yMin + removeButtonRect.height / 2f); + + GUILayout.EndVertical(); + GUILayout.Label("", GUILayout.Width(removeButtonRect.width)); + + if (GUI.Button(removeButtonRect, "x", removeButtonStyle)) + OnRemoveComponentAt(index); + + if (!string.IsNullOrEmpty(description)) + { + Rect tooltipIconRect = TooltipIconRect; + tooltipIconRect.center = new Vector2(lastrect.xMax - removeButtonRect.width / 2f, lastrect.yMin + removeButtonRect.height / 2f); + GUIContent descriptionLabel = new GUIContent(EcsUnityConsts.INFO_MARK, description); + GUI.Label(tooltipIconRect, descriptionLabel, EditorStyles.boldLabel); + } + GUILayout.EndHorizontal(); + } + + private void DrawDamagedComponent(SerializedProperty componentRefProp, int index) + { + GUILayout.BeginHorizontal(); + + EditorGUILayout.HelpBox($"Damaged component. If the problem occurred after renaming a component or initializer. use MovedFromAttrubute", MessageType.Warning); + + Rect lastrect = GUILayoutUtility.GetLastRect(); + Rect removeButtonRect = RemoveButtonRect; + removeButtonRect.center = new Vector2(lastrect.xMax + removeButtonRect.width, lastrect.yMin + removeButtonRect.height / 2f); + + GUILayout.Label("", GUILayout.Width(removeButtonRect.width)); + if (GUI.Button(removeButtonRect, "x", removeButtonStyle)) + OnRemoveComponentAt(index); + + GUILayout.EndHorizontal(); + } + + public string GetLastPathComponent(string input) + { + int lastSlashIndex = input.LastIndexOfAny(new char[] { '/', '\\' }); + if (lastSlashIndex == -1) + return input; + else + return input.Substring(lastSlashIndex + 1); + } + } + + [CustomEditor(typeof(EntityTemplatePreset), true)] + public class EntityTemplatePresetEditor : EntityTemplateEditorBase + { + public override void OnInspectorGUI() + { + Draw((ITemplateInternal)target); + } + } + [CustomEditor(typeof(EntityTemplate), true)] + public class EntityTemplateEditor : EntityTemplateEditorBase + { + public override void OnInspectorGUI() + { + Draw((ITemplateInternal)target); + } + } + } +#endif +} diff --git a/src/EntityTemplate/EntityTemplateEditor.cs.meta b/src/EntityTemplate/EntityTemplateEditor.cs.meta new file mode 100644 index 0000000..2f5abbb --- /dev/null +++ b/src/EntityTemplate/EntityTemplateEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90b44ae9b0ef473488a3befa9a120d61 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EntityTemplate/EntityTemplatePreset.cs b/src/EntityTemplate/EntityTemplatePreset.cs new file mode 100644 index 0000000..7f81632 --- /dev/null +++ b/src/EntityTemplate/EntityTemplatePreset.cs @@ -0,0 +1,24 @@ +using UnityEngine; + +namespace DCFApixels.DragonECS +{ + [CreateAssetMenu(fileName = "EntityTemplatePreset", menuName = EcsConsts.FRAMEWORK_NAME + "/EntityTemplatePreset", order = 1)] + public class EntityTemplatePreset : ScriptableObject, ITemplateInternal + { + [SerializeReference] + private ITemplateComponent[] _components; + string ITemplateInternal.ComponentsPropertyName => nameof(_components); + + //ITemplateBrowsable[] ITemplateInternal.Components + //{ + // get => _components; + // set => _components = value; + //} + + public void Apply(EcsWorld world, int entityID) + { + foreach (var item in _components) + item.Add(world, entityID); + } + } +} diff --git a/src/EntityTemplate/EntityTemplatePreset.cs.meta b/src/EntityTemplate/EntityTemplatePreset.cs.meta new file mode 100644 index 0000000..10e7c2d --- /dev/null +++ b/src/EntityTemplate/EntityTemplatePreset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54d84d8749e68c044b4f13a512808a67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EntityTemplate/ITemplate.cs b/src/EntityTemplate/ITemplate.cs new file mode 100644 index 0000000..c5290c8 --- /dev/null +++ b/src/EntityTemplate/ITemplate.cs @@ -0,0 +1,25 @@ +using UnityEngine; + +namespace DCFApixels.DragonECS +{ + public interface ITemplate + { + public void Apply(EcsWorld world, int entityID); + } + + public interface ITemplateInternal : ITemplate + { + // internal ITemplateBrowsable[] Components { get; set; } + internal string ComponentsPropertyName { get; } + } + + public static class ITemplateExt + { + public static int NewEntity(this ITemplate self, EcsWorld world) + { + int e = world.NewEntity(); + self.Apply(world, e); + return e; + } + } +} diff --git a/src/EntityTemplate/ITemplate.cs.meta b/src/EntityTemplate/ITemplate.cs.meta new file mode 100644 index 0000000..c3e7e3c --- /dev/null +++ b/src/EntityTemplate/ITemplate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33a7d50d86178eb43a36d5e8bdd70982 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EntityTemplate/TemplateComponent.cs b/src/EntityTemplate/TemplateComponent.cs new file mode 100644 index 0000000..6a2502a --- /dev/null +++ b/src/EntityTemplate/TemplateComponent.cs @@ -0,0 +1,131 @@ +using DCFApixels.DragonECS.Editors; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace DCFApixels.DragonECS +{ + public interface ITemplateComponent + { + public void Add(EcsWorld w, int e); + } + public interface ITemplateComponentName : ITemplateComponent + { + public string Name { get; } + } + + [Serializable] + public abstract class TemplateComponentInitializerBase + { + public virtual string Name => string.Empty; + public virtual Color Color => Color.black; + public virtual string Description => string.Empty; + public abstract Type Type { get; } + + #region Get meta + internal static Color GetColor(Type type) + { + var atr = type.GetCustomAttribute(); + if (atr == null) return Color.black; + return atr.GetUnityColor(); + } + internal static string GetName(Type type) + { + string friendlyName = type.Name; + if (type.IsGenericType) + { + int iBacktick = friendlyName.IndexOf('`'); + if (iBacktick > 0) + friendlyName = friendlyName.Remove(iBacktick); + + friendlyName += "/" + friendlyName; + friendlyName += "<"; + Type[] typeParameters = type.GetGenericArguments(); + for (int i = 0; i < typeParameters.Length; ++i) + { + string typeParamName = GetName(typeParameters[i]); + friendlyName += (i == 0 ? typeParamName : "," + typeParamName); + } + friendlyName += ">"; + } + return friendlyName; + } + + internal static string GetDescription(Type type) + { + var atr = type.GetCustomAttribute(); + if (atr == null) return string.Empty; + return atr.description; + } + #endregion + } + [Serializable] + public abstract class TemplateComponentInitializer : TemplateComponentInitializerBase, ITemplateComponentName + { + private static string _autoname = GetName(typeof(T)); + private static Color _autoColor = GetColor(typeof(T)); + private static string _autoDescription = GetDescription(typeof(T)); + + [SerializeField] + protected T component; + + #region Properties + public override string Name => _autoname; + public override Color Color => _autoColor; + public override string Description => _autoDescription; + public sealed override Type Type => typeof(T); + #endregion + + public abstract void Add(EcsWorld w, int e); + } + + internal static class ITemplateBrowsableExt + { + private static MethodInfo memberwiseCloneMethdo = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic); + internal static ITemplateComponent Clone(this ITemplateComponent obj) + { + return (ITemplateComponent)memberwiseCloneMethdo.Invoke(obj, null); + } + } + +#if UNITY_EDITOR + namespace Editors + { + internal static class TemplateBrowsableTypeCache + { + private static Type[] _types; + private static ITemplateComponent[] _dummies; + internal static ReadOnlySpan Types => _types; + internal static ReadOnlySpan Dummies => _dummies; + + static TemplateBrowsableTypeCache() + { + List types = new List(); + Type interfaceType = typeof(ITemplateComponent); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + var targetTypes = assembly.GetTypes().Where(type => !type.IsGenericType && (type.IsValueType|| type.IsClass) && type.GetCustomAttribute() != null); + + types.AddRange(targetTypes.Where(type => interfaceType.IsAssignableFrom(type))); + + foreach (var t in targetTypes) + { + if (t.IsSubclassOf(typeof(TemplateComponentInitializer<>))) + { + if(t.GetCustomAttribute() != null) + types.Add(t); + } + } + } + _types = types.ToArray(); + _dummies = new ITemplateComponent[_types.Length]; + + for (int i = 0; i < _types.Length; i++) + _dummies[i] = (ITemplateComponent)Activator.CreateInstance(_types[i]); + } + } + } +#endif +} diff --git a/src/EntityTemplate/TemplateComponent.cs.meta b/src/EntityTemplate/TemplateComponent.cs.meta new file mode 100644 index 0000000..77e0d5d --- /dev/null +++ b/src/EntityTemplate/TemplateComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ce52308e352f734e8a21c5ae282c246 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Extensions.meta b/src/Extensions.meta new file mode 100644 index 0000000..4a9e845 --- /dev/null +++ b/src/Extensions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 37d966ee996491b4d923ae68af4b67cd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Extensions/EcsEntityConnect.cs b/src/Extensions/EcsEntityConnect.cs new file mode 100644 index 0000000..ddd2041 --- /dev/null +++ b/src/Extensions/EcsEntityConnect.cs @@ -0,0 +1,143 @@ +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace DCFApixels.DragonECS +{ + public class EcsEntityConnect : MonoBehaviour + { + private sealed class Subject : EcsSubject + { + public readonly EcsPool unityGameObjects; + public Subject(Builder b) + { + unityGameObjects = b.Include(); + } + } + + private entlong _entity; + private EcsWorld _world; + + [SerializeField] + private EntityTemplatePreset[] _entityTemplatePresets; + [SerializeField] + private EntityTemplate[] _entityTemplates; + + internal void SetTemplates_Editor(EntityTemplate[] tempaltes) + { + _entityTemplates = tempaltes; + } + + #region Properties + public entlong Entity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _entity; + } + public EcsWorld World + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _world; + } + public bool IsAlive + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _entity.IsAlive; + } + #endregion + + public void ConnectWith(entlong entity, bool applyTemplates = false) + { + if(_entity.TryGetID(out int oldE) && _world != null) + { + var s = _world.GetSubject(); + s.unityGameObjects.Del(oldE); + } + _world = null; + + if (entity.TryGetID(out int newE)) + { + _entity = entity; + _world = _entity.World; + var s = _world.GetSubject(); + if (!s.unityGameObjects.Has(newE)) s.unityGameObjects.Add(newE) = new UnityGameObject(gameObject); + + if (applyTemplates) + ApplyTemplates(); + } + else + { + _entity = entlong.NULL; + } + } + public void ApplyTemplates() => ApplyTemplatesFor(_entity.ID); + public void ApplyTemplatesFor(int entityID) + { + foreach (var t in _entityTemplatePresets) + t.Apply(_world, entityID); + foreach (var t in _entityTemplates) + t.Apply(_world, entityID); + } + } + +#if UNITY_EDITOR + + namespace Editors + { + using UnityEditor; + [CustomEditor(typeof(EcsEntityConnect))] + public class EcsEntityEditor : Editor + { + private EcsEntityConnect Target => (EcsEntityConnect)target; + private GUIStyle _greenStyle; + private GUIStyle _redStyle; + + + private bool _isInit = false; + + private void Init() + { + if (_isInit) + return; + + _greenStyle = EcsEditor.GetStyle(new Color32(75, 255, 0, 100)); + _redStyle = EcsEditor.GetStyle(new Color32(255, 0, 75, 100)); + + + _isInit = true; + } + + public override void OnInspectorGUI() + { + Init(); + if (Target.IsAlive) + GUILayout.Box("Connected", _greenStyle, GUILayout.ExpandWidth(true)); + else + GUILayout.Box("Not connected", _redStyle, GUILayout.ExpandWidth(true)); + + if(Target.Entity.TryGetID(out int id)) + EditorGUILayout.IntField(id); + else + EditorGUILayout.IntField(0); + GUILayout.Label(Target.Entity.ToString()); + + base.OnInspectorGUI(); + + if(GUILayout.Button("Autoset Templates")) + { + Target.SetTemplates_Editor(Target.GetComponents()); + + EditorUtility.SetDirty(target); + } + if (GUILayout.Button("Autoset Templates Cascade")) + { + foreach (var item in Target.GetComponentsInChildren()) + { + item.SetTemplates_Editor(item.GetComponents()); + EditorUtility.SetDirty(item); + } + } + } + } + } +#endif +} diff --git a/src/Extensions/EcsEntityConnect.cs.meta b/src/Extensions/EcsEntityConnect.cs.meta new file mode 100644 index 0000000..aff4433 --- /dev/null +++ b/src/Extensions/EcsEntityConnect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cdc92b01ccc1e684f955830aa7cea7d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Runners/Runners.cs b/src/Extensions/Runners.cs similarity index 88% rename from src/Runners/Runners.cs rename to src/Extensions/Runners.cs index 12f3a6e..94a5731 100644 --- a/src/Runners/Runners.cs +++ b/src/Extensions/Runners.cs @@ -2,7 +2,7 @@ namespace DCFApixels.DragonECS { - public interface IEcsLateRunSystem : IEcsSystem + public interface IEcsLateRunProcess : IEcsSystem { public void LateRun(EcsPipeline pipeline); } @@ -10,10 +10,10 @@ namespace DCFApixels.DragonECS { public static void LateRun(this EcsPipeline systems) { - systems.GetRunner().LateRun(systems); + systems.GetRunner().LateRun(systems); } } - public interface IEcsFixedRunSystem : IEcsSystem + public interface IEcsFixedRunProcess : IEcsSystem { public void FixedRun(EcsPipeline pipeline); } @@ -21,14 +21,14 @@ namespace DCFApixels.DragonECS { public static void FixedRun(this EcsPipeline pipeline) { - pipeline.GetRunner().FixedRun(pipeline); + pipeline.GetRunner().FixedRun(pipeline); } } namespace Internal { [DebugColor(DebugColor.Orange)] - public class EcsLateRunSystemRunner : EcsRunner, IEcsLateRunSystem + public class EcsLateRunSystemRunner : EcsRunner, IEcsLateRunProcess { #if DEBUG && !DISABLE_DEBUG private EcsProfilerMarker[] _markers; @@ -58,7 +58,7 @@ namespace DCFApixels.DragonECS #endif } [DebugColor(DebugColor.Orange)] - public class EcsFixedRunSystemRunner : EcsRunner, IEcsFixedRunSystem + public class EcsFixedRunSystemRunner : EcsRunner, IEcsFixedRunProcess { #if DEBUG && !DISABLE_DEBUG private EcsProfilerMarker[] _markers; diff --git a/src/Runners/Runners.cs.meta b/src/Extensions/Runners.cs.meta similarity index 100% rename from src/Runners/Runners.cs.meta rename to src/Extensions/Runners.cs.meta diff --git a/src/Extensions/Systems.cs b/src/Extensions/Systems.cs new file mode 100644 index 0000000..4296026 --- /dev/null +++ b/src/Extensions/Systems.cs @@ -0,0 +1,56 @@ +namespace DCFApixels.DragonECS +{ + [DebugHide, DebugColor(DebugColor.Grey)] + public class DeleteOneFrameComponentFixedSystem : IEcsFixedRunProcess, IEcsInject + where TWorld : EcsWorld + where TComponent : struct, IEcsComponent + { + private TWorld _world; + public void Inject(TWorld obj) => _world = obj; + + private sealed class Subject : EcsSubject + { + public EcsPool pool; + public Subject(Builder b) + { + pool = b.Include(); + } + } + public void FixedRun(EcsPipeline pipeline) + { + foreach (var e in _world.Where(out Subject s)) + { + //try + //{ + s.pool.Del(e); + //} + //catch (System.Exception) + //{ + // + // throw; + //} + } + } + } + + public static class DeleteOneFrameComponentFixedSystemExt + { + private const string AUTO_DEL_FIXED_LAYER = nameof(AUTO_DEL_FIXED_LAYER); + public static EcsPipeline.Builder AutoDelFixed(this EcsPipeline.Builder b) + where TWorld : EcsWorld + where TComponent : struct, IEcsComponent + { + b.Layers.Insert(EcsConsts.POST_END_LAYER, AUTO_DEL_FIXED_LAYER); + b.AddUnique(new DeleteOneFrameComponentFixedSystem(), AUTO_DEL_FIXED_LAYER); + return b; + } + /// for EcsDefaultWorld + public static EcsPipeline.Builder AutoDelFixed(this EcsPipeline.Builder b) + where TComponent : struct, IEcsComponent + { + b.Layers.Insert(EcsConsts.POST_END_LAYER, AUTO_DEL_FIXED_LAYER); + b.AddUnique(new DeleteOneFrameComponentFixedSystem(), AUTO_DEL_FIXED_LAYER); + return b; + } + } +} diff --git a/src/Extensions/Systems.cs.meta b/src/Extensions/Systems.cs.meta new file mode 100644 index 0000000..21f2963 --- /dev/null +++ b/src/Extensions/Systems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e8c608fea9f3569409826ec54affa822 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Extensions/UnityComponents.cs b/src/Extensions/UnityComponents.cs new file mode 100644 index 0000000..2ea1928 --- /dev/null +++ b/src/Extensions/UnityComponents.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Scripting.APIUpdating; + +namespace DCFApixels.DragonECS +{ + [Serializable] + [DebugColor(255 / 3, 255, 0)] + public struct UnityComponent : IEcsComponent, IEnumerable//IntelliSense hack + where T : class + { + public T obj; + + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); //IntelliSense hack + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); //IntelliSense hack + } + + [Serializable] + [MovedFrom(false, "Client", null, "RefRigitBodyInitializer")] + public sealed class UnityComponentRigitBodyInitializer : TemplateComponentInitializer> + { + public override void Add(EcsWorld w, int e) => w.GetPool>().Add(e) = component; + } + + [Serializable] + [MovedFrom(false, "Client", null, "RefAnimatorInitializer")] + public sealed class UnityComponentAnimatorInitializer : TemplateComponentInitializer> + { + public override void Add(EcsWorld w, int e) => w.GetPool>().Add(e) = component; + } + [Serializable] + public sealed class UnityComponentCharacterControllerInitializer : TemplateComponentInitializer> + { + public override void Add(EcsWorld w, int e) => w.GetPool>().Add(e) = component; + } + + #region Colliders + [Serializable] + public sealed class UnityComponentColliderInitializer : TemplateComponentInitializer> + { + public override string Name => "UnityComponent/Collider/" + nameof(Collider); + public override void Add(EcsWorld w, int e) => w.GetPool>().Add(e) = component; + } + [Serializable] + public sealed class UnityComponentBoxColliderInitializer : TemplateComponentInitializer> + { + public override string Name => "UnityComponent/Collider/" + nameof(BoxCollider); + public override void Add(EcsWorld w, int e) => w.GetPool>().Add(e) = component; + } + [Serializable] + public sealed class UnityComponentSphereColliderInitializer : TemplateComponentInitializer> + { + public override string Name => "UnityComponent/Collider/" + nameof(SphereCollider); + public override void Add(EcsWorld w, int e) => w.GetPool>().Add(e) = component; + } + [Serializable] + public sealed class UnityComponentCapsuleColliderInitializer : TemplateComponentInitializer> + { + public override string Name => "UnityComponent/Collider/" + nameof(CapsuleCollider); + public override void Add(EcsWorld w, int e) => w.GetPool>().Add(e) = component; + } + [Serializable] + public sealed class UnityComponentMeshColliderInitializer : TemplateComponentInitializer> + { + public override string Name => "UnityComponent/Collider/" + nameof(MeshCollider); + public override void Add(EcsWorld w, int e) => w.GetPool>().Add(e) = component; + } + #endregion + +} diff --git a/src/Extensions/UnityComponents.cs.meta b/src/Extensions/UnityComponents.cs.meta new file mode 100644 index 0000000..a32753d --- /dev/null +++ b/src/Extensions/UnityComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47a8547ed46c26e4bb6a68651ad40be0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnityGameObject.cs b/src/Extensions/UnityGameObject.cs similarity index 50% rename from src/UnityGameObject.cs rename to src/Extensions/UnityGameObject.cs index bba3011..5433fce 100644 --- a/src/UnityGameObject.cs +++ b/src/Extensions/UnityGameObject.cs @@ -1,7 +1,5 @@ using System.Runtime.CompilerServices; using UnityEngine; -using System.Linq; -using UnityEditor.ShortcutManagement; #if UNITY_EDITOR using UnityEditor; #endif @@ -62,11 +60,11 @@ namespace DCFApixels.DragonECS public static class GameObjectRefExt { - public static EcsEntity NewEntityWithGameObject(this EcsWorld self, string name = "EcsEntity", GameObjectIcon icon = GameObjectIcon.NONE) + public static entlong NewEntityWithGameObject(this EcsWorld self, string name = "EcsEntity", GameObjectIcon icon = GameObjectIcon.NONE) { - EcsEntity result = self.NewEntity(); + entlong result = self.GetEntityLong(self.NewEntity()); GameObject newGameObject = new GameObject(name); - newGameObject.AddComponent().ConectWith(result); + newGameObject.AddComponent().ConnectWith(result); // self.GetPool().Add(result.id) = #if UNITY_EDITOR if (icon != GameObjectIcon.NONE) @@ -90,76 +88,4 @@ namespace DCFApixels.DragonECS return result; } } - - - public class EcsEntityConnect : MonoBehaviour - { - private sealed class Query : EcsQuery - { - public readonly EcsPool unityGameObjects; - public Query(Builder b) - { - unityGameObjects = b.Include(); - } - } - - private EcsEntity _entity; - private EcsWorld _world; - - #region Properties - public EcsEntity Entity - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _entity; - } - public EcsWorld World - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _world; - } - public bool IsAlive - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _entity.IsAlive; - } - #endregion - - public void ConectWith(EcsEntity entity) - { - int e = _entity.id; - if (_world != null && _entity.IsNotNull) - { - var q = _world.Select(); - q.unityGameObjects.Del(e); - } - _world = null; - - _entity = entity; - - if (_entity.IsNotNull) - { - _world = _entity.GetWorld(); - var q = _world.Select(); - if (!q.unityGameObjects.Has(e)) q.unityGameObjects.Add(e) = new UnityGameObject(gameObject); - } - } - } - -#if UNITY_EDITOR - - namespace Editors - { - using UnityEditor; - [CustomEditor(typeof(EcsEntityConnect))] - public class EcsEntityEditor : Editor - { - private EcsEntityConnect Target => (EcsEntityConnect)target; - public override void OnInspectorGUI() - { - EditorGUILayout.IntField(Target.Entity.id); - GUILayout.Label(Target.Entity.ToString()); - } - } - } -#endif } diff --git a/src/UnityGameObject.cs.meta b/src/Extensions/UnityGameObject.cs.meta similarity index 100% rename from src/UnityGameObject.cs.meta rename to src/Extensions/UnityGameObject.cs.meta diff --git a/src/Utils/BitMask.cs b/src/Utils/BitMask.cs new file mode 100644 index 0000000..7096bfe --- /dev/null +++ b/src/Utils/BitMask.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS.Editors +{ + internal class BitMask + { + private const int OFFSET = 5; + private const int MOD_MASK = 31; + private const int DATA_BITS = 32; + private int[] _data; + + private int _size; + + public BitMask(int size) + { + _data = Array.Empty(); + Resize(size); + } + + public bool this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (_data[index >> OFFSET] & (1 << (index & MOD_MASK))) != 0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + if(value) + _data[index >> OFFSET] |= (1 << (index & MOD_MASK)); + else + _data[index >> OFFSET] &= ~(1 << (index & MOD_MASK)); + } + } + + public void Resize(int newSize) + { + if (newSize <= _size) + return; + + _size = newSize / DATA_BITS + 1; + Array.Resize(ref _data, _size); + } + } +} diff --git a/src/Utils/BitMask.cs.meta b/src/Utils/BitMask.cs.meta new file mode 100644 index 0000000..520453f --- /dev/null +++ b/src/Utils/BitMask.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f3449fe3fe92a747b97743ed020758e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/SparseArray.cs b/src/Utils/SparseArray.cs new file mode 100644 index 0000000..5699919 --- /dev/null +++ b/src/Utils/SparseArray.cs @@ -0,0 +1,212 @@ +//SparseArray. Analogous to Dictionary, but faster. +//Benchmark result of indexer.get speed test with 300 elements: +//[Dictinary: 5.786us] [SparseArray: 2.047us]. +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace DCFApixels.DragonECS.Editors +{ + internal class SparseArray + { + public const int MIN_CAPACITY_BITS_OFFSET = 4; + public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET; + private const int EMPTY = -1; + + private int[] _buckets = Array.Empty(); + private Entry[] _entries = Array.Empty(); + + private int _count; + + private int _freeList; + private int _freeCount; + + private int _modBitMask; + + #region Properties + public TValue this[int key] + { + get => _entries[FindEntry(key)].value; + set => Insert(key, value); + } + + public int Count => _count; + #endregion + + #region Constructors + public SparseArray(int minCapacity = MIN_CAPACITY) + { + minCapacity = NormalizeCapacity(minCapacity); + _buckets = new int[minCapacity]; + for (int i = 0; i < minCapacity; i++) + _buckets[i] = EMPTY; + _entries = new Entry[minCapacity]; + _modBitMask = (minCapacity - 1) & 0x7FFFFFFF; + } + #endregion + + #region Add + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int key, TValue value) + { +#if DEBUG + if (Contains(key)) + throw new ArgumentException("Contains(hashKey) is true"); +#endif + Insert(key, value); + } + #endregion + + #region Find/Insert/Remove + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int FindEntry(int key) + { + for (int i = _buckets[key & _modBitMask]; i >= 0; i = _entries[i].next) + if (_entries[i].hashKey == key) return i; + return -1; + } + private void Insert(int key, TValue value) + { + int targetBucket = key & _modBitMask; + + for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next) + { + if (_entries[i].hashKey == key) + { + _entries[i].value = value; + return; + } + } + + int index; + if (_freeCount > 0) + { + index = _freeList; + _freeList = _entries[index].next; + _freeCount--; + } + else + { + if (_count == _entries.Length) + { + Resize(); + targetBucket = key & _modBitMask; + } + index = _count++; + } + + _entries[index].next = _buckets[targetBucket]; + _entries[index].hashKey = key; + _entries[index].value = value; + _buckets[targetBucket] = index; + } + public bool Remove(int key) + { + int bucket = key & _modBitMask; + int last = -1; + for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].next) + { + if (_entries[i].hashKey == key) + { + if (last < 0) + { + _buckets[bucket] = _entries[i].next; + } + else + { + _entries[last].next = _entries[i].next; + } + _entries[i].next = _freeList; + _entries[i].hashKey = -1; + _entries[i].value = default; + _freeList = i; + _freeCount++; + return true; + } + } + return false; + } + #endregion + + #region TryGetValue + public bool TryGetValue(int key, out TValue value) + { + int index = FindEntry(key); + if (index < 0) + { + value = default; + return false; + } + value = _entries[index].value; + return true; + } + #endregion + + #region Contains + public bool Contains(int key) + { + return FindEntry(key) >= 0; + } + #endregion + + #region Clear + public void Clear() + { + if (_count > 0) + { + for (int i = 0; i < _buckets.Length; i++) + { + _buckets[i] = -1; + } + Array.Clear(_entries, 0, _count); + _count = 0; + } + } + #endregion + + #region Resize + private void Resize() + { + int newSize = _buckets.Length << 1; + _modBitMask = (newSize - 1) & 0x7FFFFFFF; + + Contract.Assert(newSize >= _entries.Length); + int[] newBuckets = new int[newSize]; + for (int i = 0; i < newBuckets.Length; i++) + newBuckets[i] = EMPTY; + + Entry[] newEntries = new Entry[newSize]; + Array.Copy(_entries, 0, newEntries, 0, _count); + for (int i = 0; i < _count; i++) + { + if (newEntries[i].hashKey >= 0) + { + int bucket = newEntries[i].hashKey % newSize; + newEntries[i].next = newBuckets[bucket]; + newBuckets[bucket] = i; + } + } + _buckets = newBuckets; + _entries = newEntries; + } + + private int NormalizeCapacity(int capacity) + { + int result = MIN_CAPACITY; + while (result < capacity) result <<= 1; + return result; + } + #endregion + + #region Utils + [StructLayout(LayoutKind.Sequential, Pack = 4)] + private struct Entry + { + public int next; // Index of next entry, -1 if last + public int hashKey; + public TValue value; + } + #endregion + } +} \ No newline at end of file diff --git a/src/Utils/SparseArray.cs.meta b/src/Utils/SparseArray.cs.meta new file mode 100644 index 0000000..e20cd32 --- /dev/null +++ b/src/Utils/SparseArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7493e8f4d9d69e8478883dfd50d3ccee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: