diff --git a/Addons/Features/Editor/SystemsCodeGenerator.cs b/Addons/Features/Editor/SystemsCodeGenerator.cs index a57f2f57..9c14d667 100644 --- a/Addons/Features/Editor/SystemsCodeGenerator.cs +++ b/Addons/Features/Editor/SystemsCodeGenerator.cs @@ -10,6 +10,18 @@ namespace ME.BECS.Editor.Systems { public class SystemsCodeGenerator : CustomCodeGenerator { + private static string[] _cachedGraphGuids; + private static System.DateTime _graphsCacheTime; + + private static string[] GetCachedGraphGuids() { + var now = System.DateTime.Now; + if (_cachedGraphGuids == null || (now - _graphsCacheTime).TotalSeconds > 5) { + _cachedGraphGuids = UnityEditor.AssetDatabase.FindAssets("t:SystemsGraph"); + _graphsCacheTime = now; + } + return _cachedGraphGuids; + } + private void AddMethod(SystemsGraph graph, string baseName, string methodName, Method method, out scg::List content, out scg::List innerMethods) where T : class { //var name = System.Text.RegularExpressions.Regex.Replace(graph.name, @"(\s+|@|&|'|\(|\)|<|>|#|-)", "_"); content = new scg::List(); @@ -48,7 +60,7 @@ public string AddContent(scg::List filesContent) { var content = new scg::List(); if (this.editorAssembly == false) { - var graphs = UnityEditor.AssetDatabase.FindAssets("t:SystemsGraph"); + var graphs = GetCachedGraphGuids(); if (filesContent != null) { foreach (var guid in graphs) { var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid); @@ -164,6 +176,7 @@ public string AddContent(scg::List filesContent) { content.Add("}"); content.Add("[UnityEngine.Scripting.PreserveAttribute]"); content.Add("public static void SystemsLoad() {"); + graphs = GetCachedGraphGuids(); foreach (var guid in graphs) { var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid); var graph = UnityEditor.AssetDatabase.LoadAssetAtPath(path); @@ -245,12 +258,18 @@ public string GetArgString() { } public string GetWriteOpString(string key) { - var index = this.keyToIndex[key]; + if (this.keyToIndex.TryGetValue(key, out var index) == false) { + this.Add(key); + index = this.keyToIndex[key]; + } return $"dependencies[{index}] = {key};"; } public string GetReadOpString(string key) { - var index = this.keyToIndex[key]; + if (this.keyToIndex.TryGetValue(key, out var index) == false) { + this.Add(key); + index = this.keyToIndex[key]; + } return $"dependencies[{index}]"; } @@ -539,6 +558,7 @@ n is ME.BECS.Extensions.GraphProcessor.RelayNode || // Parallel mode var srcDep = index; methodContent.Add($"var depsGeneric{srcDep.ToString()} = new NativeArray({types.Length}, Constants.ALLOCATOR_TEMP);"); + collectedDeps.Add($"dep{srcDep.ToString()}"); var withoutSync = true; foreach (var cType in types) { var type = systemType.MakeGenericType(cType); @@ -548,7 +568,6 @@ n is ME.BECS.Extensions.GraphProcessor.RelayNode || methodContent.Add($"(({EditorUtils.GetTypeName(type)}*)systems[{(index.globalIndex + index.genericIndex)}])->{method}(ref systemContext);"); methodContent.Add($"depsGeneric{srcDep.ToString()}[{index.genericIndex}] = systemContext.dependsOn;"); methodContent.Add("}"); - if (index.genericIndex == 0) collectedDeps.Add($"dep{indexStr}"); printedDependencies.Add($"dep{indexStr}"); if (withoutSync == true) { var syncState = IsSyncNotRequired(type); @@ -572,6 +591,7 @@ n is ME.BECS.Extensions.GraphProcessor.RelayNode || var type = systemType.MakeGenericType(cType); methodContent.Add($"systemContext = SystemContext.Create(dt, in world, localDependsOn);"); methodContent.Add($"(({EditorUtils.GetTypeName(type)}*)systems[{(index.globalIndex + index.genericIndex)}])->{method}(ref systemContext);"); + collectedDeps.Add($"dep{indexStr}"); var withoutSync = IsSyncNotRequired(type); AddApply(systemNode, index, ref schemeDependsOn, "systemContext.dependsOn", "localDependsOn", forceWithoutSync: withoutSync); index.AddGeneric(); @@ -914,8 +934,17 @@ private static int GetNodeIndex(SystemsGraph graph, ME.BECS.Extensions.GraphProc return index; } - public static int InitializeGraph(CustomCodeGenerator generator, scg::Dictionary systemTypeToVar, scg::List content, SystemsGraph graph, int rootGraphId, int index) { - for (int idx = 0; idx < graph.nodes.Count; ++idx) { + public static int InitializeGraph( + CustomCodeGenerator generator, + scg::Dictionary systemTypeToVar, + scg::List content, + SystemsGraph graph, + int rootGraphId, + int index + ) + { + for (int idx = 0; idx < graph.nodes.Count; ++idx) + { var node = graph.nodes[idx]; if (node is ME.BECS.FeaturesGraph.Nodes.SystemNode systemNode) { var system = systemNode.system; @@ -942,7 +971,9 @@ public static int InitializeGraph(CustomCodeGenerator generator, scg::Dictionary ++index; } } - } else { + } + else + { var systemTypeStr = EditorUtils.GetTypeName(systemType); content.Add("{"); content.Add($"var item = allocator.Allocate(TSize<{systemTypeStr}>.sizeInt, TAlign<{systemTypeStr}>.alignInt);"); @@ -955,8 +986,17 @@ public static int InitializeGraph(CustomCodeGenerator generator, scg::Dictionary ++index; } } - } else if (node is ME.BECS.FeaturesGraph.Nodes.GraphNode graphNode) { - index = InitializeGraph(generator, systemTypeToVar, content, graphNode.graphValue, rootGraphId, index); + } + else if (node is ME.BECS.FeaturesGraph.Nodes.GraphNode graphNode) + { + index = InitializeGraph( + generator, + systemTypeToVar, + content, + graphNode.graphValue, + rootGraphId, + index + ); } } @@ -1050,7 +1090,7 @@ public static void InjectDependencies(SystemsGraph graph, scg::List cont containsBool = true; } if (typeof(IInject).IsAssignableFrom(field.FieldType) == true) { - var jobTypeStr = EditorUtils.GetTypeName(jobType); + var jobTypeStr = EditorUtils.GetJobTypeName(jobType); localContent.Add($"JobInject<{jobTypeStr}>.Init();"); break; } @@ -1066,7 +1106,7 @@ public static void InjectDependencies(SystemsGraph graph, scg::List cont foreach (var jobType in types) { if (jobType.IsVisible == false) continue; var fields = jobType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - var jobTypeStr = EditorUtils.GetTypeName(jobType); + var jobTypeStr = EditorUtils.GetJobTypeName(jobType); var containsBool = false; localContent.Clear(); foreach (var field in fields) { @@ -1110,7 +1150,8 @@ private static void CollectJobsTypes(MethodInfo root, scg::HashSet var visited = new scg::HashSet(); while (q.Count > 0) { var body = q.Dequeue(); - var instructions = body.GetInstructions(); + var instructions = CodeGenerator.GetCachedInstructions(body); + if (instructions == null) continue; foreach (var inst in instructions) { if (inst.Operand is MethodInfo methodInfo) { if ((methodInfo.Name == "Schedule" || methodInfo.Name == "ScheduleSingleWithInject" || methodInfo.Name == "ScheduleSingleWithInjectByRef") && methodInfo.IsGenericMethod == true) { diff --git a/Editor/CodeGenerator/CodeGenerator.cs b/Editor/CodeGenerator/CodeGenerator.cs index b88ab993..8c983f22 100644 --- a/Editor/CodeGenerator/CodeGenerator.cs +++ b/Editor/CodeGenerator/CodeGenerator.cs @@ -1,5 +1,7 @@ +using System; using System.Reflection; using Newtonsoft.Json; +using ME.BECS.Mono.Reflection; namespace ME.BECS.Editor { @@ -64,10 +66,12 @@ public struct FileContent { public class Cache { + private static readonly System.Collections.Generic.Dictionary _md5Cache = new System.Collections.Generic.Dictionary(); + [System.Serializable] public struct CachedItem { - public string[] hashCodes; + public System.Collections.Generic.HashSet hashCodes; [UnityEngine.SerializeReference] public object data; @@ -101,7 +105,7 @@ public override string ToString() { public void Add(System.Type type, T data) { - var scriptsPath = ScriptsImporter.FindScript(type); + var scriptsPath = CodeGenerator.GetCachedScriptPath(type); if (scriptsPath == null) return; foreach (string scriptPath in scriptsPath) { var hashCode = scriptPath != null ? Md5(scriptPath) : null; @@ -109,14 +113,14 @@ public void Add(System.Type type, T data) { { var key = new Key(type, this.method, this.key).ToString(); if (this.cacheData.TryGetValue(key, out var item) == true) { - if (System.Array.IndexOf(item.hashCodes, hashCode) == -1) { - System.Array.Resize(ref item.hashCodes, item.hashCodes.Length + 1); - item.hashCodes[^1] = hashCode; + if (item.hashCodes == null) { + item.hashCodes = new System.Collections.Generic.HashSet(); } + item.hashCodes.Add(hashCode); this.cacheData[key] = item; } else { this.cacheData.Add(key, new CachedItem() { - hashCodes = new[] { hashCode }, + hashCodes = new System.Collections.Generic.HashSet { hashCode }, data = data, }); } @@ -129,12 +133,12 @@ public void Add(System.Type type, T data) { public bool TryGetValue(System.Type key, out T value) { var cacheIsInvalid = true; if (this.cacheData.TryGetValue(new Key(key, this.method, this.key).ToString(), out var cachedItem) == true) { - var scriptsPath = ScriptsImporter.FindScript(key); - if (scriptsPath != null) { + var scriptsPath = CodeGenerator.GetCachedScriptPath(key); + if (scriptsPath != null && cachedItem.hashCodes != null) { cacheIsInvalid = false; foreach (string scriptPath in scriptsPath) { var monoScriptHashCode = scriptPath != null ? Md5(scriptPath) : null; - if (System.Array.IndexOf(cachedItem.hashCodes, monoScriptHashCode) == -1) { + if (monoScriptHashCode == null || cachedItem.hashCodes.Contains(monoScriptHashCode) == false) { cacheIsInvalid = true; break; } @@ -170,13 +174,19 @@ public void SetKey(string key) { } private static string Md5(string scriptPath) { - var text = UnityEditor.AssetDatabase.LoadAssetAtPath(scriptPath)?.text; - if (text == null) return null; - using (var md5 = System.Security.Cryptography.MD5.Create()) { - var bytes = System.Text.Encoding.UTF8.GetBytes(text); - var computeHash = md5.ComputeHash(bytes); - return System.BitConverter.ToString(computeHash); + if (string.IsNullOrEmpty(scriptPath)) return null; + if (_md5Cache.TryGetValue(scriptPath, out var cachedHash)) { + return cachedHash; } + if (!System.IO.File.Exists(scriptPath)) return null; + var lastWrite = System.IO.File.GetLastWriteTime(scriptPath).Ticks; + var hash = lastWrite.ToString(); + _md5Cache[scriptPath] = hash; + return hash; + } + + internal static void ClearMd5Cache() { + _md5Cache.Clear(); } internal void Load(string dir, string filename) { @@ -184,18 +194,56 @@ internal void Load(string dir, string filename) { this.dir = dir; this.filename = filename; this.isDirty = false; - //var ms = System.Diagnostics.Stopwatch.StartNew(); var path = $"{this.dir}/{this.filename}"; var loadedCache = System.IO.File.Exists(path) == true ? System.IO.File.ReadAllText(path) : null; if (loadedCache == null) { this.cacheData = new System.Collections.Generic.Dictionary(); } else { - this.cacheData = Newtonsoft.Json.JsonConvert.DeserializeObject>(loadedCache); + try { + this.cacheData = Newtonsoft.Json.JsonConvert.DeserializeObject>(loadedCache); + if (this.cacheData != null) { + foreach (var kvp in this.cacheData) { + var item = kvp.Value; + if (item.hashCodes == null) { + item.hashCodes = new System.Collections.Generic.HashSet(); + } + this.cacheData[kvp.Key] = item; + } + } + } catch { + try { + var oldData = Newtonsoft.Json.JsonConvert.DeserializeObject>(loadedCache); + if (oldData != null) { + this.cacheData = new System.Collections.Generic.Dictionary(); + foreach (var kvp in oldData) { + var hashSet = new System.Collections.Generic.HashSet(); + if (kvp.Value.hashCodes != null) { + foreach (var hash in kvp.Value.hashCodes) { + hashSet.Add(hash); + } + } + this.cacheData[kvp.Key] = new CachedItem { + hashCodes = hashSet, + data = kvp.Value.data + }; + } + this.isDirty = true; + } + } catch { + this.cacheData = new System.Collections.Generic.Dictionary(); + } + } } - //UnityEngine.Debug.Log($"Cache {this.filename} loaded in {ms.ElapsedMilliseconds}ms"); } + [System.Serializable] + private struct OldCachedItem { + public string[] hashCodes; + [UnityEngine.SerializeReference] + public object data; + } + internal void SetMethod(string method) { this.method = method; // this.cacheData.Clear(); @@ -449,11 +497,11 @@ public static void RegenerateBurstAOT(bool forced = false, bool cleanCache = fal var list = EditorUtils.GetAssembliesInfo(); { var dir = $"Assets/{ECS}.Gen/Runtime"; - Build(list, dir); + Build(list, dir, editorAssembly: false, forced: forced); } { var dir = $"Assets/{ECS}.Gen/Editor"; - Build(list, dir, editorAssembly: true); + Build(list, dir, editorAssembly: true, forced: forced); } } @@ -463,29 +511,71 @@ private static void CleanCache() { if (System.IO.Directory.Exists($"Assets/{ECS}.Gen/Runtime/Cache") == false) return; System.IO.Directory.Delete($"Assets/{ECS}.Gen/Runtime/Cache", true); System.IO.Directory.Delete($"Assets/{ECS}.Gen/Editor/Cache", true); + Cache.ClearMd5Cache(); } - private static bool HasComponentCustomSharedHash(System.Type type) { + private static readonly System.Collections.Generic.Dictionary _isTagTypeCache = new System.Collections.Generic.Dictionary(); + private static readonly System.Collections.Generic.Dictionary _hasCustomHashCache = new System.Collections.Generic.Dictionary(); + private static readonly System.Collections.Generic.Dictionary _typeNameCache = new System.Collections.Generic.Dictionary(); + private static readonly System.Collections.Generic.Dictionary _assemblyNameCache = new System.Collections.Generic.Dictionary(); + private static readonly System.Collections.Generic.Dictionary _typeCacheDerivedCache = new System.Collections.Generic.Dictionary(); + private static readonly System.Collections.Generic.Dictionary _fieldsCache = new System.Collections.Generic.Dictionary(); + private static readonly System.Collections.Generic.Dictionary, System.Reflection.MethodInfo> _methodCache = new System.Collections.Generic.Dictionary, System.Reflection.MethodInfo>(); + private static readonly System.Collections.Generic.Dictionary, System.Reflection.InterfaceMapping> _interfaceMapCache = new System.Collections.Generic.Dictionary, System.Reflection.InterfaceMapping>(); + internal static readonly System.Collections.Generic.Dictionary> _instructionsCache = new System.Collections.Generic.Dictionary>(); + internal static readonly System.Collections.Generic.Dictionary> _methodTypesInfoCache = new System.Collections.Generic.Dictionary>(); + internal static readonly System.Collections.Generic.Dictionary _jobEntInfoCache = new System.Collections.Generic.Dictionary(); + internal static readonly System.Collections.Generic.Dictionary _jobWeightsInfoCache = new System.Collections.Generic.Dictionary(); + private static readonly System.Collections.Generic.Dictionary _interfacesCache = new System.Collections.Generic.Dictionary(); + private static readonly System.Collections.Generic.Dictionary _findScriptCache = new System.Collections.Generic.Dictionary(); - var m = type.GetMethod(nameof(IComponentShared.GetHash), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + private static bool HasComponentCustomSharedHash(System.Type type) { + if (_hasCustomHashCache.TryGetValue(type, out var cached)) { + return cached; + } + var m = GetCachedMethod(type, nameof(IComponentShared.GetHash), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + bool result; if (m == null) { - var hasMethod = type.GetInterfaceMap(typeof(IComponentShared)).TargetMethods.Any(m => m.IsPrivate == true && m.Name == typeof(IComponentShared).FullName + "." + nameof(IComponentShared.GetHash)); - return hasMethod; + var map = GetCachedInterfaceMap(type, typeof(IComponentShared)); + var hasMethod = map.TargetMethods.Any(m => m.IsPrivate == true && m.Name == typeof(IComponentShared).FullName + "." + nameof(IComponentShared.GetHash)); + result = hasMethod; + } else { + result = true; } - return true; - + _hasCustomHashCache[type] = result; + return result; } private static bool IsTagType(System.Type type) { - + if (_isTagTypeCache.TryGetValue(type, out var cached)) { + return cached; + } + bool result = false; if (System.Runtime.InteropServices.Marshal.SizeOf(type) <= 1 && - type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Length == 0) { - return true; + GetCachedFields(type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Length == 0) { + result = true; } + _isTagTypeCache[type] = result; + return result; + } - return false; + private static string GetCachedTypeName(System.Type type) { + if (_typeNameCache.TryGetValue(type, out var cached)) { + return cached; + } + var name = EditorUtils.GetTypeName(type); + _typeNameCache[type] = name; + return name; + } + private static string GetCachedAssemblyName(System.Type type) { + if (_assemblyNameCache.TryGetValue(type, out var cached)) { + return cached; + } + var name = type.Assembly.GetName().Name; + _assemblyNameCache[type] = name; + return name; } private static void OnLogAdded(string condition, string stackTrace, UnityEngine.LogType type) { @@ -507,7 +597,93 @@ private static void OnLogAdded(string condition, string stackTrace, UnityEngine. public const string PROGRESS_BAR_CAPTION = "[ ME.BECS ] CodeGenerator"; - private static void Build(System.Collections.Generic.List asms, string dir, bool editorAssembly = false) { + public static System.Type[] GetCachedTypesDerivedFrom(System.Type baseType) { + if (_typeCacheDerivedCache.TryGetValue(baseType, out var cached)) { + return cached; + } + var types = UnityEditor.TypeCache.GetTypesDerivedFrom(baseType).ToArray(); + _typeCacheDerivedCache[baseType] = types; + return types; + } + + internal static System.Reflection.FieldInfo[] GetCachedFields(System.Type type, System.Reflection.BindingFlags flags) { + var key = type; + if (_fieldsCache.TryGetValue(key, out var cached)) { + return cached; + } + var fields = type.GetFields(flags); + _fieldsCache[key] = fields; + return fields; + } + + internal static System.Reflection.MethodInfo GetCachedMethod(System.Type type, string methodName, System.Reflection.BindingFlags flags) { + var key = System.Tuple.Create(type, methodName, flags); + if (_methodCache.TryGetValue(key, out var cached)) { + return cached; + } + var method = type.GetMethod(methodName, flags); + _methodCache[key] = method; + return method; + } + + internal static System.Reflection.InterfaceMapping GetCachedInterfaceMap(System.Type type, System.Type interfaceType) { + var key = System.Tuple.Create(type, interfaceType); + if (_interfaceMapCache.TryGetValue(key, out var cached)) { + return cached; + } + var map = type.GetInterfaceMap(interfaceType); + _interfaceMapCache[key] = map; + return map; + } + + public static System.Collections.Generic.List GetCachedInstructions(System.Reflection.MethodInfo method) { + if (method == null || method.GetMethodBody() == null) return null; + if (_instructionsCache.TryGetValue(method, out var cached)) { + return cached; + } + var instructions = method.GetInstructions().ToList(); + _instructionsCache[method] = instructions; + return instructions; + } + + internal static System.Type[] GetCachedInterfaces(System.Type type) { + if (_interfacesCache.TryGetValue(type, out var cached)) { + return cached; + } + var interfaces = type.GetInterfaces(); + _interfacesCache[type] = interfaces; + return interfaces; + } + + internal static string[] GetCachedScriptPath(System.Type type) { + if (_findScriptCache.TryGetValue(type, out var cached)) { + return cached; + } + var paths = ScriptsImporter.FindScript(type); + _findScriptCache[type] = paths ?? System.Array.Empty(); + return paths; + } + + private static void Build(System.Collections.Generic.List asms, string dir, bool editorAssembly = false, bool forced = false) { + + var sw = System.Diagnostics.Stopwatch.StartNew(); + + if (forced == true) { + _isTagTypeCache.Clear(); + _hasCustomHashCache.Clear(); + _typeNameCache.Clear(); + _assemblyNameCache.Clear(); + _typeCacheDerivedCache.Clear(); + _fieldsCache.Clear(); + _methodCache.Clear(); + _interfaceMapCache.Clear(); + _instructionsCache.Clear(); + _methodTypesInfoCache.Clear(); + _jobEntInfoCache.Clear(); + _jobWeightsInfoCache.Clear(); + _interfacesCache.Clear(); + _findScriptCache.Clear(); + } string postfix; if (editorAssembly == true) { @@ -516,7 +692,9 @@ private static void Build(System.Collections.Generic.List asms, st postfix = "Runtime"; } - var customCodeGenerators = UnityEditor.TypeCache.GetTypesDerivedFrom().OrderBy(x => x.GetCustomAttribute()?.order).ThenBy(x => x.FullName); + var asmsDict = asms.ToDictionary(x => x.name, x => x); + + var customCodeGenerators = GetCachedTypesDerivedFrom(typeof(CustomCodeGenerator)).OrderBy(x => x.GetCustomAttribute()?.order).ThenBy(x => x.FullName); var generators = customCodeGenerators.Select(x => (CustomCodeGenerator)System.Activator.CreateInstance(x)).ToArray(); if (System.IO.Directory.Exists(dir) == false) { @@ -544,7 +722,7 @@ private static void Build(System.Collections.Generic.List asms, st //var template = "namespace " + ECS + " {\n [UnityEngine.Scripting.PreserveAttribute] public static unsafe class AOTBurstHelper { \n[UnityEngine.Scripting.PreserveAttribute] \npublic static void AOT() { \n{{CONTENT}} \n}\n }\n }"; var aotContent = new System.Collections.Generic.List(); var typesContent = new System.Collections.Generic.List(); - var types = UnityEditor.TypeCache.GetTypesDerivedFrom(typeof(ISystem)).OrderBy(x => x.FullName).ToList(); + var types = GetCachedTypesDerivedFrom(typeof(ISystem)).OrderBy(x => x.FullName).ToList(); PatchSystemsList(types); var burstedTypes = UnityEditor.TypeCache.GetTypesWithAttribute(); var burstDiscardedTypes = UnityEditor.TypeCache.GetMethodsWithAttribute(); @@ -563,14 +741,13 @@ private static void Build(System.Collections.Generic.List asms, st var type = types[index]; if (type.IsValueType == false) continue; - var asm = type.Assembly; - var name = asm.GetName().Name; - var info = asms.FirstOrDefault(x => x.name == name); + var asmName = GetCachedAssemblyName(type); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; if (editorAssembly == false && info.isEditor == true) continue; if (type.IsVisible == false) continue; - var systemType = EditorUtils.GetTypeName(type); + var systemType = GetCachedTypeName(type); aotContent.Add($"StaticSystemTypes<{systemType}>.Validate();"); typesContent.Add($"StaticSystemTypes<{systemType}>.Validate();"); @@ -629,13 +806,13 @@ private static void Build(System.Collections.Generic.List asms, st var components = UnityEditor.TypeCache.GetTypesWithAttribute().OrderBy(x => x.FullName).ToArray(); foreach (var component in components) { - var asm = component.Assembly.GetName().Name; - var info = asms.FirstOrDefault(x => x.name == asm); + var asmName = GetCachedAssemblyName(component); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; if (editorAssembly == false && info.isEditor == true) continue; var attr = (ComponentGroupAttribute)component.GetCustomAttribute(typeof(ComponentGroupAttribute)); - var systemType = EditorUtils.GetTypeName(component); - var groupType = EditorUtils.GetTypeName(attr.groupType); + var systemType = GetCachedTypeName(component); + var groupType = GetCachedTypeName(attr.groupType); var str = $"StaticTypes<{systemType}>.ApplyGroup(typeof({groupType}));"; typesContent.Add(str); componentTypes.Add(component); @@ -643,18 +820,19 @@ private static void Build(System.Collections.Generic.List asms, st } { - var allComponents = UnityEditor.TypeCache.GetTypesDerivedFrom().OrderBy(x => x.FullName).ToArray(); + var allComponents = GetCachedTypesDerivedFrom(typeof(IComponent)).OrderBy(x => x.FullName).ToArray(); foreach (var component in allComponents) { if (component.IsValueType == false) continue; + if (component.IsGenericTypeDefinition) continue; - var asm = component.Assembly.GetName().Name; - var info = asms.FirstOrDefault(x => x.name == asm); + var asmName = GetCachedAssemblyName(component); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; if (editorAssembly == false && info.isEditor == true) continue; var isTagType = IsTagType(component); var isTag = isTagType.ToString().ToLower(); - var type = EditorUtils.GetTypeName(component); + var type = GetCachedTypeName(component); { var str = $"StaticTypes<{type}>.Validate(isTag: {isTag});"; typesContent.Add(str); @@ -672,18 +850,39 @@ private static void Build(System.Collections.Generic.List asms, st } } { - var allComponents = UnityEditor.TypeCache.GetTypesDerivedFrom().OrderBy(x => x.FullName).ToArray(); + var allComponents = GetCachedTypesDerivedFrom(typeof(IComponentDestroy)).OrderBy(x => x.FullName).ToArray(); foreach (var component in allComponents) { if (component.IsValueType == false) continue; - var asm = component.Assembly.GetName().Name; - var info = asms.FirstOrDefault(x => x.name == asm); + var asmName = GetCachedAssemblyName(component); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; + if (editorAssembly == false && info.isEditor == true) continue; + + var isTagType = IsTagType(component); + var isTag = isTagType.ToString().ToLower(); + var type = GetCachedTypeName(component); + var str = $"StaticTypesDestroy<{type}>.RegisterAutoDestroy(isTag: {isTag});"; + typesContent.Add(str); + componentTypes.Add(component); + aotContent.Add($"AutoDestroyRegistryStatic<{type}>.Destroy(default, null);"); + + } + } + { + var allComponents = GetCachedTypesDerivedFrom(typeof(IComponentShared)).OrderBy(x => x.FullName).ToArray(); + foreach (var component in allComponents) { + + if (component.IsValueType == false) continue; + if (component.IsGenericTypeDefinition) continue; + + var asmName = GetCachedAssemblyName(component); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; if (editorAssembly == false && info.isEditor == true) continue; var isTag = IsTagType(component).ToString().ToLower(); var hasCustomHash = HasComponentCustomSharedHash(component); - var type = EditorUtils.GetTypeName(component); + var type = GetCachedTypeName(component); var str = $"StaticTypes<{type}>.ValidateShared(isTag: {isTag}, hasCustomHash: {hasCustomHash.ToString().ToLower()});"; typesContent.Add(str); componentTypes.Add(component); @@ -692,17 +891,18 @@ private static void Build(System.Collections.Generic.List asms, st } } { - var allComponents = UnityEditor.TypeCache.GetTypesDerivedFrom().OrderBy(x => x.FullName).ToArray(); + var allComponents = GetCachedTypesDerivedFrom(typeof(IConfigComponentStatic)).OrderBy(x => x.FullName).ToArray(); foreach (var component in allComponents) { if (component.IsValueType == false) continue; + if (component.IsGenericTypeDefinition) continue; - var asm = component.Assembly.GetName().Name; - var info = asms.FirstOrDefault(x => x.name == asm); + var asmName = GetCachedAssemblyName(component); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; if (editorAssembly == false && info.isEditor == true) continue; var isTag = IsTagType(component).ToString().ToLower(); - var type = EditorUtils.GetTypeName(component); + var type = GetCachedTypeName(component); var str = $"StaticTypes<{type}>.ValidateStatic(isTag: {isTag});"; typesContent.Add(str); componentTypes.Add(component); @@ -711,13 +911,14 @@ private static void Build(System.Collections.Generic.List asms, st } } { - var allComponents = UnityEditor.TypeCache.GetTypesDerivedFrom().OrderBy(x => x.FullName).ToArray(); + var allComponents = GetCachedTypesDerivedFrom(typeof(IConfigInitialize)).OrderBy(x => x.FullName).ToArray(); foreach (var component in allComponents) { if (component.IsValueType == false) continue; + if (component.IsGenericTypeDefinition) continue; - var asm = component.Assembly.GetName().Name; - var info = asms.FirstOrDefault(x => x.name == asm); + var asmName = GetCachedAssemblyName(component); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; if (editorAssembly == false && info.isEditor == true) continue; var isTag = IsTagType(component).ToString().ToLower(); @@ -734,6 +935,7 @@ private static void Build(System.Collections.Generic.List asms, st var publicContent = new scg::List(); var filesContent = new scg::List(); { + var initBeforeCount = componentTypes.Count; var cache = new Cache(); for (var index = 0; index < generators.Length; ++index) { var customCodeGenerator = generators[index]; @@ -751,13 +953,58 @@ private static void Build(System.Collections.Generic.List asms, st cache.SetMethod("AddPublicContent"); publicContent.Add(customCodeGenerator.AddPublicContent()); cache.SetMethod("AddFileContent"); + var fileBeforeCount = componentTypes.Count; var files = customCodeGenerator.AddFileContent(componentTypes); if (files != null) filesContent.Add(files); cache.SetMethod("AddMethods"); + var methodsBeforeCount = componentTypes.Count; methods.AddRange(customCodeGenerator.AddMethods(componentTypes)); + + cache.Push(); componentTypes.Add(customCodeGenerator.GetType()); } + + var componentValidationsToInsert = new System.Collections.Generic.List(); + var validatedTypes = new System.Collections.Generic.HashSet(); + for (int i = initBeforeCount; i < componentTypes.Count; ++i) { + var addedType = componentTypes[i]; + if (addedType.IsValueType == false) continue; + if (addedType.IsGenericTypeDefinition) continue; + if (typeof(IComponent).IsAssignableFrom(addedType) == false) continue; + if (validatedTypes.Contains(addedType)) continue; + + var asmName = GetCachedAssemblyName(addedType); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; + if (editorAssembly == false && info.isEditor == true) continue; + if (addedType.IsVisible == false) continue; + + validatedTypes.Add(addedType); + var isTagType = IsTagType(addedType); + var isTag = isTagType.ToString().ToLower(); + var type = GetCachedTypeName(addedType); + + if (typeof(IConfigComponentStatic).IsAssignableFrom(addedType)) { + componentValidationsToInsert.Add($"StaticTypes<{type}>.ValidateStatic(isTag: {isTag});"); + aotContent.Add($"StaticTypesStatic<{type}>.AOT();"); + } else if (typeof(IComponentShared).IsAssignableFrom(addedType)) { + var hasCustomHash = HasComponentCustomSharedHash(addedType); + componentValidationsToInsert.Add($"StaticTypes<{type}>.ValidateShared(isTag: {isTag}, hasCustomHash: {hasCustomHash.ToString().ToLower()});"); + aotContent.Add($"StaticTypesShared<{type}>.AOT();"); + } else { + componentValidationsToInsert.Add($"StaticTypes<{type}>.Validate(isTag: {isTag});"); + aotContent.Add($"StaticTypes<{type}>.AOT();"); + } + } + + if (componentValidationsToInsert.Count > 0) { + var aspectValidationIndex = typesContent.FindIndex(x => x.Contains("AspectTypeInfo<") && x.Contains(".Validate()")); + if (aspectValidationIndex >= 0) { + typesContent.InsertRange(aspectValidationIndex, componentValidationsToInsert); + } else { + typesContent.AddRange(componentValidationsToInsert); + } + } } var methodRegistryContents = methods.Where(x => x.definition != null && x.type != null) @@ -818,6 +1065,8 @@ private static void Build(System.Collections.Generic.List asms, st UnityEngine.Debug.LogException(ex); } finally { UnityEditor.EditorUtility.ClearProgressBar(); + sw.Stop(); + Logger.Editor.Log($"[CodeGenerator] Build {dir} took {sw.ElapsedMilliseconds}ms"); } { var csc = @$"{dir}/csc.rsp"; @@ -845,27 +1094,27 @@ private static void Build(System.Collections.Generic.List asms, st } var content = new scg::HashSet(); - var types = UnityEditor.TypeCache.GetTypesDerivedFrom(typeof(ISystem)); + var types = GetCachedTypesDerivedFrom(typeof(ISystem)); foreach (var type in types) { - var asm = type.Assembly.GetName().Name; - var info = asms.FirstOrDefault(x => x.name == asm); + var asmName = GetCachedAssemblyName(type); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; if (editorAssembly == false && info.isEditor == true) continue; - content.Add(asm); + content.Add(asmName); } foreach (var type in componentTypes) { - var asm = type.Assembly.GetName().Name; - var info = asms.FirstOrDefault(x => x.name == asm); + var asmName = GetCachedAssemblyName(type); + if (!asmsDict.TryGetValue(asmName, out var info)) continue; if (editorAssembly == false && info.isEditor == true) continue; - content.Add(asm); + content.Add(asmName); } // load references foreach (var asm in content.ToArray()) { - var asmInfo = asms.FirstOrDefault(x => x.name == asm); + if (!asmsDict.TryGetValue(asm, out var asmInfo)) continue; if (asmInfo.references != null) { foreach (var refAsm in asmInfo.references) { - var info = asms.FirstOrDefault(x => x.name == refAsm); + if (!asmsDict.TryGetValue(refAsm, out var info)) continue; if (editorAssembly == false && info.isEditor == true) continue; content.Add(refAsm); } @@ -898,7 +1147,7 @@ public static void PatchSystemsList(System.Collections.Generic.List --index; var typeGen = EditorUtils.GetFirstInterfaceConstraintType(type); if (typeGen != null) { - var genTypes = UnityEditor.TypeCache.GetTypesDerivedFrom(typeGen).OrderBy(x => x.FullName).ToArray(); + var genTypes = GetCachedTypesDerivedFrom(typeGen).OrderBy(x => x.FullName).ToArray(); foreach (var genType in genTypes) { if (genType.IsValueType == false) continue; var gType = type.MakeGenericType(genType); diff --git a/Editor/CodeGenerator/Generators/AspectsCodeGenerator.cs b/Editor/CodeGenerator/Generators/AspectsCodeGenerator.cs index e4c06841..ebe90c0d 100644 --- a/Editor/CodeGenerator/Generators/AspectsCodeGenerator.cs +++ b/Editor/CodeGenerator/Generators/AspectsCodeGenerator.cs @@ -5,67 +5,140 @@ namespace ME.BECS.Editor.Aspects { public class AspectsCodeGenerator : CustomCodeGenerator { + private struct AspectCacheData { + public System.Collections.Generic.List validations; + public string[] componentTypeNames; + } + public override void AddInitialization(System.Collections.Generic.List dataList, System.Collections.Generic.List references) { - var content = new System.Collections.Generic.List(); + var componentTypesToValidate = new System.Collections.Generic.List(); + var aspectValidations = new System.Collections.Generic.List(); var aspects = UnityEditor.TypeCache.GetTypesDerivedFrom(typeof(IAspect)).OrderBy(x => x.FullName).ToArray(); + this.cache.SetKey("AspectData"); foreach (var aspect in aspects) { - - if (this.cache.TryGetValue>(aspect, out var cacheData) == true) { - content.AddRange(cacheData); - continue; - } - + if (aspect.IsValueType == false) continue; if (aspect.IsVisible == false) continue; if (this.IsValidTypeForAssembly(aspect, true) == false) continue; - var contentItem = new System.Collections.Generic.List(); var type = aspect; - var strType = EditorUtils.GetTypeName(type); - var types = new System.Collections.Generic.List(); - var fieldsCount = 0; - var fields = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); - foreach (var field in fields) { - var fieldType = field.FieldType; - if (typeof(IAspectData).IsAssignableFrom(fieldType) == true && - field.GetCustomAttribute(typeof(QueryWithAttribute)) != null) { - ++fieldsCount; - var gType = fieldType.GenericTypeArguments[0]; - if (gType.IsVisible == false) continue; - types.Add(EditorUtils.GetTypeName(gType)); - references.Add(gType); + AspectCacheData aspectData; + if (this.cache.TryGetValue(aspect, out var cachedData) == false) { + var fields = CodeGenerator.GetCachedFields(type, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).OrderBy(x => x.FieldType.FullName).ToArray(); + + var contentItem = new System.Collections.Generic.List(); + var componentTypesFromAspect = new System.Collections.Generic.List(); + var strType = EditorUtils.GetDataTypeName(type); + var types = new System.Collections.Generic.List(); + var fieldsCount = 0; + foreach (var field in fields) { + var fieldType = field.FieldType; + if (typeof(IAspectData).IsAssignableFrom(fieldType) == true) { + var gType = fieldType.GenericTypeArguments[0]; + + if (gType.IsVisible == false) { + continue; + } + if (gType.IsGenericTypeDefinition) { + continue; + } + if (this.IsValidTypeForAssembly(gType) == false) { + continue; + } + + if (typeof(IComponent).IsAssignableFrom(gType)) { + componentTypesFromAspect.Add(gType); + } + + if (references.Contains(gType) == false) { + references.Add(gType); + } + + if (field.GetCustomAttribute(typeof(QueryWithAttribute)) != null) { + ++fieldsCount; + types.Add(EditorUtils.GetDataTypeName(gType)); + } + } } - } - var str = $"AspectTypeInfo<{strType}>.Validate();"; - contentItem.Add(str); - if (fieldsCount > 0 && fieldsCount == types.Count) { - references.Add(type); - str = $"AspectTypeInfo.with.Get(AspectTypeInfo<{strType}>.typeId).Resize({types.Count});"; + var str = $"AspectTypeInfo<{strType}>.Validate();"; contentItem.Add(str); - for (int i = 0; i < types.Count; ++i) { - str = $"AspectTypeInfo.with.Get(AspectTypeInfo<{strType}>.typeId).Get({i}) = StaticTypes<{types[i]}>.typeId;"; + if (fieldsCount > 0 && fieldsCount == types.Count) { + references.Add(type); + var aspectVarName = EditorUtils.GetCodeName(strType); + str = $"var {aspectVarName} = AspectTypeInfo<{strType}>.typeId;"; + contentItem.Add(str); + str = $"AspectTypeInfo.with.Get({aspectVarName}).Resize({types.Count});"; + contentItem.Add(str); + for (int i = 0; i < types.Count; ++i) { + str = $"AspectTypeInfo.with.Get({aspectVarName}).Get({i}) = StaticTypes<{types[i]}>.typeId;"; + contentItem.Add(str); + } + } + + aspectData = new AspectCacheData { + validations = contentItem, + componentTypeNames = componentTypesFromAspect.Select(t => t.AssemblyQualifiedName).ToArray() + }; + this.cache.Add(aspect, aspectData); + } else { + aspectData = cachedData; + if (aspectData.componentTypeNames != null) { + foreach (var typeName in aspectData.componentTypeNames) { + var gType = System.Type.GetType(typeName); + if (gType != null) { + if (references.Contains(gType) == false) { + references.Add(gType); + } + componentTypesToValidate.Add(gType); + } + } } } - - this.cache.Add(aspect, contentItem); - content.AddRange(contentItem); + aspectValidations.AddRange(aspectData.validations); } this.cache.Push(); - - dataList.AddRange(content); + var validatedComponentTypes = new System.Collections.Generic.HashSet(); + foreach (var componentType in componentTypesToValidate.Distinct().Where(x => x != null)) { + if (!validatedComponentTypes.Add(componentType)) continue; + + var componentTypeName = EditorUtils.GetDataTypeName(componentType); + var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic; + var isTag = (System.Runtime.InteropServices.Marshal.SizeOf(componentType) <= 1 && + CodeGenerator.GetCachedFields(componentType, flags).Length == 0).ToString().ToLower(); + + string validationCall; + if (typeof(IConfigComponentStatic).IsAssignableFrom(componentType)) { + validationCall = $"StaticTypes<{componentTypeName}>.ValidateStatic(isTag: {isTag});"; + } else if (typeof(IComponentShared).IsAssignableFrom(componentType)) { + var hasCustomHash = CodeGenerator.GetCachedMethod(componentType, nameof(IComponentShared.GetHash), flags) != null || + CodeGenerator.GetCachedInterfaceMap(componentType, typeof(IComponentShared)).TargetMethods.Any(m => m.IsPrivate == true && m.Name == typeof(IComponentShared).FullName + "." + nameof(IComponentShared.GetHash)); + validationCall = $"StaticTypes<{componentTypeName}>.ValidateShared(isTag: {isTag}, hasCustomHash: {hasCustomHash.ToString().ToLower()});"; + } else { + validationCall = $"StaticTypes<{componentTypeName}>.Validate(isTag: {isTag});"; + } + dataList.Add(validationCall); + } + dataList.AddRange(aspectValidations); } + private struct AspectMethodCacheData { + public string content; + public string[] componentTypeNames; + } + public override System.Collections.Generic.List AddMethods(System.Collections.Generic.List references) { var content = new System.Collections.Generic.List(); var aspects = UnityEditor.TypeCache.GetTypesDerivedFrom(typeof(IAspect)).OrderBy(x => x.FullName).ToArray(); + var componentsFromMethods = new System.Collections.Generic.HashSet(); + this.cache.SetKey("AspectMethods"); foreach (var aspect in aspects) { if (aspect.IsValueType == false) continue; @@ -74,28 +147,67 @@ public override void AddInitialization(System.Collections.Generic.List d if (this.IsValidTypeForAssembly(aspect, true) == false) continue; var type = aspect; - var strType = EditorUtils.GetTypeName(type); - var types = new System.Collections.Generic.List(); - var fieldsCount = 0; - var fields = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).OrderBy(x => x.FieldType.FullName).ToArray(); - foreach (var field in fields) { - var fieldType = field.FieldType; - if (typeof(IAspectData).IsAssignableFrom(fieldType) == true) { + string aspectMethodContent; + AspectMethodCacheData cacheData; + if (this.cache.TryGetValue(aspect, out cacheData) == false) { + var strType = EditorUtils.GetDataTypeName(type); + var types = new System.Collections.Generic.List(); + var fieldsCount = 0; + var componentTypesFromAspect = new System.Collections.Generic.List(); + var fields = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).OrderBy(x => x.FieldType.FullName).ToArray(); + foreach (var field in fields) { + var fieldType = field.FieldType; + if (typeof(IAspectData).IsAssignableFrom(fieldType) != true) + continue; ++fieldsCount; var gType = fieldType.GenericTypeArguments[0]; if (gType.IsVisible == false) continue; + if (gType.IsGenericTypeDefinition) continue; + if (this.IsValidTypeForAssembly(gType) == false) continue; + if (typeof(IComponent).IsAssignableFrom(gType)) { + componentTypesFromAspect.Add(gType); + componentsFromMethods.Add(gType); + if (references.Contains(gType) == false) { + references.Add(gType); + } + } var fieldOffset = System.Runtime.InteropServices.Marshal.OffsetOf(type, field.Name); - var t = $"{EditorUtils.GetDataTypeName(fieldType)}<{EditorUtils.GetTypeName(gType)}>"; - types.Add($"*(({t}*)((addr + {fieldOffset}).ptr)) = new ME.BECS.AspectDataPtr<{EditorUtils.GetTypeName(gType)}>(in world);"); + var t = EditorUtils.FormatGenericTypeWithSingleArgument(fieldType.GetGenericTypeDefinition(), gType); + types.Add($"*(({t}*)(addr + {fieldOffset})) = new ME.BECS.AspectDataPtr<{EditorUtils.GetDataTypeName(gType)}>(in world);"); } - } - - if (fieldsCount > 0) { - var str = $@"{{ -var addr = WorldAspectStorage.Initialize(world.id, AspectTypeInfo<{strType}>.typeId, TSize<{strType}>.size); + + if (fieldsCount > 0) { + aspectMethodContent = $@"{{ +ref var aspect = ref world.InitializeAspect<{strType}>(); +var addr = (byte*)_addressPtr(ref aspect); {string.Join("\n", types)} }}"; - content.Add(str); + cacheData = new AspectMethodCacheData { + content = aspectMethodContent, + componentTypeNames = componentTypesFromAspect.Select(t => t.AssemblyQualifiedName).ToArray() + }; + this.cache.Add(aspect, cacheData); + } else { + aspectMethodContent = null; + cacheData = default; + } + } else { + aspectMethodContent = cacheData.content; + if (cacheData.componentTypeNames != null) { + foreach (var typeName in cacheData.componentTypeNames) { + var gType = System.Type.GetType(typeName); + if (gType != null) { + componentsFromMethods.Add(gType); + if (references.Contains(gType) == false) { + references.Add(gType); + } + } + } + } + } + + if (aspectMethodContent != null) { + content.Add(aspectMethodContent); } } diff --git a/Editor/CodeGenerator/Generators/CopyFromCodeGenerator.cs b/Editor/CodeGenerator/Generators/CopyFromCodeGenerator.cs index d59b3321..c9d0ef06 100644 --- a/Editor/CodeGenerator/Generators/CopyFromCodeGenerator.cs +++ b/Editor/CodeGenerator/Generators/CopyFromCodeGenerator.cs @@ -34,8 +34,9 @@ public class CopyFromCodeGenerator : CustomCodeGenerator { contentItem.Add("/*{"); contentItem.Add($"var source = ({strType}*)componentPtr;"); contentItem.Add($"ref var target = ref ent.Get<{strType}>();"); + var typeStr = EditorUtils.FormatGenericTypeWithSingleArgument(fieldType, gType); contentItem.Add( - $"target.{field.Name} = new {EditorUtils.GetDataTypeName(fieldType)}<{EditorUtils.GetTypeName(gType)}>(in ent, in source->{field.Name});"); + $"target.{field.Name} = new {typeStr}(in ent, in source->{field.Name});"); var cloneMethod = gType.GetMethod("CopyFrom"); if (cloneMethod != null && cloneMethod.GetParameters().Length == 2) { var p = cloneMethod.GetParameters(); diff --git a/Editor/CodeGenerator/Generators/EntityConfigCodeGenerator.cs b/Editor/CodeGenerator/Generators/EntityConfigCodeGenerator.cs index 731727cc..6708d1d4 100644 --- a/Editor/CodeGenerator/Generators/EntityConfigCodeGenerator.cs +++ b/Editor/CodeGenerator/Generators/EntityConfigCodeGenerator.cs @@ -15,12 +15,20 @@ public override void AddInitialization(System.Collections.Generic.List d var allStaticComponents = UnityEditor.TypeCache.GetTypesDerivedFrom().OrderBy(x => x.FullName).ToArray(); var allSharedComponents = UnityEditor.TypeCache.GetTypesDerivedFrom().OrderBy(x => x.FullName).ToArray(); allComponents = allComponents.Concat(allStaticComponents).Concat(allSharedComponents).ToArray(); + this.cache.SetKey("CollectionsCount"); foreach (var component in allComponents) { if (component.IsValueType == false) continue; if (this.IsValidTypeForAssembly(component) == false) continue; - var collectionsCount = GetCollectionsCount(component); + uint collectionsCount; + if (this.cache.TryGetValue(component, out var cachedCount) == false) { + collectionsCount = GetCollectionsCount(component); + this.cache.Add(component, collectionsCount); + } else { + collectionsCount = cachedCount; + } + if (collectionsCount == 0u) continue; var type = EditorUtils.GetTypeName(component); var str = $"StaticTypes<{type}>.SetCollectionsCount({collectionsCount}u);"; @@ -89,7 +97,7 @@ public override void AddInitialization(System.Collections.Generic.List d if (typeof(IUnmanagedList).IsAssignableFrom(fieldType) == true) { var gType = fieldType.GenericTypeArguments[0]; if (gType.IsVisible == false) continue; - var typeStr = $"{EditorUtils.GetDataTypeName(fieldType)}<{EditorUtils.GetTypeName(gType)}>"; + var typeStr = EditorUtils.FormatGenericTypeWithSingleArgument(fieldType, gType); var fieldOffset = System.Runtime.InteropServices.Marshal.OffsetOf(type, field.Name); content.Add("{"); content.Add($"var component = ({strType}*)componentPtr;"); diff --git a/Editor/CodeGenerator/Generators/GenericComponentsCodeGenerator.cs b/Editor/CodeGenerator/Generators/GenericComponentsCodeGenerator.cs new file mode 100644 index 00000000..a4690a07 --- /dev/null +++ b/Editor/CodeGenerator/Generators/GenericComponentsCodeGenerator.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace ME.BECS.Editor.Generators { + + public class GenericComponentsCodeGenerator : CustomCodeGenerator { + + private static Type[] _cachedGenericComponentDefinitions; + private static readonly System.Collections.Generic.Dictionary _cachedPossibleTypes = new System.Collections.Generic.Dictionary(); + + private struct GenericDefInfo { + public Type constraintType; + public Type[] possibleTypes; + } + + private struct ValidationInfo { + public string validationCall; + } + + public override void AddInitialization(System.Collections.Generic.List dataList, System.Collections.Generic.List references) { + _cachedGenericComponentDefinitions ??= UnityEditor.TypeCache.GetTypesDerivedFrom(typeof(IComponent)) + .Where(x => x.IsGenericTypeDefinition && x.IsValueType && typeof(IComponent).IsAssignableFrom(x)) + .ToArray(); + var genericComponentDefinitions = _cachedGenericComponentDefinitions; + + var allInstantiations = new System.Collections.Generic.HashSet(); + var referencesSet = new System.Collections.Generic.HashSet(references); + + cache.SetKey("GenericDefInfo"); + foreach (var genericDef in genericComponentDefinitions) { + + if (IsValidTypeForAssembly(genericDef, true) == false) continue; + if (genericDef.GetGenericArguments().Length != 1) continue; + + GenericDefInfo defInfo; + if (cache.TryGetValue(genericDef, out var cachedDefInfo) == false) { + var typeParams = genericDef.GetGenericArguments(); + Type constraintType = null; + Type[] allConstraints = null; + if (typeParams.Length > 0) { + allConstraints = typeParams[0].GetGenericParameterConstraints(); + constraintType = allConstraints.FirstOrDefault(x => x.IsInterface == true); + if (constraintType == null && allConstraints.Length > 0) { + constraintType = allConstraints.FirstOrDefault(x => x.IsValueType); + } + } + + Type[] possibleTypes = null; + if (constraintType != null) { + if (constraintType.IsInterface) { + if (!_cachedPossibleTypes.TryGetValue(constraintType, out possibleTypes)) { + possibleTypes = UnityEditor.TypeCache.GetTypesDerivedFrom(constraintType) + .Where(x => x.IsValueType && !x.IsGenericTypeDefinition && x.IsVisible) + .ToArray(); + _cachedPossibleTypes[constraintType] = possibleTypes; + } + } else if (constraintType.IsValueType) { + possibleTypes = new[] { constraintType }; + } + } + + defInfo = new GenericDefInfo { + constraintType = constraintType, + possibleTypes = possibleTypes ?? Type.EmptyTypes + }; + cache.Add(genericDef, defInfo); + } else { + defInfo = cachedDefInfo; + } + + if (defInfo.constraintType != null && defInfo.possibleTypes.Length > 0) { + foreach (var possibleType in defInfo.possibleTypes) { + if (IsValidTypeForAssembly(possibleType) == false) continue; + try { + var instantiated = genericDef.MakeGenericType(possibleType); + if (allInstantiations.Add(instantiated)) { + if (referencesSet.Add(instantiated)) { + references.Add(instantiated); + } + } + } catch { + } + } + } + } + + var validatedTypes = new System.Collections.Generic.HashSet(); + cache.SetKey("ValidationInfo"); + foreach (var componentType in allInstantiations.OrderBy(x => x.FullName)) { + if (!validatedTypes.Add(componentType)) continue; + + ValidationInfo validationInfo; + if (cache.TryGetValue(componentType, out var cachedValidation) == false) { + var componentTypeName = EditorUtils.GetDataTypeName(componentType); + var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + var isTag = (System.Runtime.InteropServices.Marshal.SizeOf(componentType) <= 1 && + CodeGenerator.GetCachedFields(componentType, flags).Length == 0).ToString().ToLower(); + + string validationCall; + if (typeof(IConfigComponentStatic).IsAssignableFrom(componentType)) { + validationCall = $"StaticTypes<{componentTypeName}>.ValidateStatic(isTag: {isTag});"; + } else if (typeof(IComponentShared).IsAssignableFrom(componentType)) { + var hasCustomHash = CodeGenerator.GetCachedMethod(componentType, nameof(IComponentShared.GetHash), flags) != null || + CodeGenerator.GetCachedInterfaceMap(componentType, typeof(IComponentShared)).TargetMethods.Any(m => m.IsPrivate == true && m.Name == typeof(IComponentShared).FullName + "." + nameof(IComponentShared.GetHash)); + validationCall = $"StaticTypes<{componentTypeName}>.ValidateShared(isTag: {isTag}, hasCustomHash: {hasCustomHash.ToString().ToLower()});"; + } else { + validationCall = $"StaticTypes<{componentTypeName}>.Validate(isTag: {isTag});"; + } + + validationInfo = new ValidationInfo { + validationCall = validationCall + }; + cache.Add(componentType, validationInfo); + } else { + validationInfo = cachedValidation; + } + + dataList.Add(validationInfo.validationCall); + } + + } + + } + +} + diff --git a/Editor/CodeGenerator/Generators/GenericComponentsCodeGenerator.cs.meta b/Editor/CodeGenerator/Generators/GenericComponentsCodeGenerator.cs.meta new file mode 100644 index 00000000..e8dbb842 --- /dev/null +++ b/Editor/CodeGenerator/Generators/GenericComponentsCodeGenerator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5b16c54f54528524ba653849e20fc11e \ No newline at end of file diff --git a/Editor/CodeGenerator/Generators/JobsEarlyInitCodeGenerator.cs b/Editor/CodeGenerator/Generators/JobsEarlyInitCodeGenerator.cs index e8c99087..a0d0fdbc 100644 --- a/Editor/CodeGenerator/Generators/JobsEarlyInitCodeGenerator.cs +++ b/Editor/CodeGenerator/Generators/JobsEarlyInitCodeGenerator.cs @@ -36,7 +36,7 @@ public override string ToString() { private void Generate(System.Collections.Generic.List dataList, string method) { this.cache.SetKey($"{method}:{typeof(TJobBase).Name}:{typeof(T0).Name}:{typeof(T1).Name}"); - var jobsComponents = UnityEditor.TypeCache.GetTypesDerivedFrom(typeof(TJobBase)).OrderBy(x => x.FullName).ToList(); + var jobsComponents = CodeGenerator.GetCachedTypesDerivedFrom(typeof(TJobBase)).OrderBy(x => x.FullName).ToList(); CodeGenerator.PatchSystemsList(jobsComponents); foreach (var jobType in jobsComponents) { @@ -56,10 +56,10 @@ private void Generate(System.Collections.Generic.List throw new System.Exception($"[ CodeGenerator ] Generic jobs are not supported (job type {jobType.FullName}). Use generic systems instead."); } - var jobTypeFullName = EditorUtils.GetTypeName(jobType); + var jobTypeFullName = EditorUtils.GetJobTypeName(jobType); var components = new System.Collections.Generic.List(); var componentsTypes = new System.Collections.Generic.List(); - var jobInterfaces = jobType.GetInterfaces(); + var jobInterfaces = CodeGenerator.GetCachedInterfaces(jobType); System.Type workInterface = null; foreach (var i in jobInterfaces) { if (i.IsGenericType == true) { @@ -67,8 +67,12 @@ private void Generate(System.Collections.Generic.List if (typeof(T0).IsAssignableFrom(type) == true || typeof(T1).IsAssignableFrom(type) == true) { if (this.IsValidTypeForAssembly(type) == false) continue; + if (type.IsGenericTypeDefinition) continue; components.Add(EditorUtils.GetDataTypeName(type)); componentsTypes.Add(type); + if (this.references != null) { + this.references.Add(type); + } } } @@ -92,6 +96,23 @@ private void Generate(System.Collections.Generic.List } var typeInfos = GetJobTypesInfo(jobType); + var allComponents = new System.Collections.Generic.HashSet(); + foreach (var item in typeInfos) { + if (typeof(IComponent).IsAssignableFrom(item.type) == false) continue; + if (item.type.IsGenericTypeDefinition) continue; + if (this.IsValidTypeForAssembly(item.type) == false) continue; + allComponents.Add(item.type); + } + foreach (var componentType in componentsTypes) { + if (typeof(IComponent).IsAssignableFrom(componentType) == false) continue; + if (componentType.IsGenericTypeDefinition) continue; + allComponents.Add(componentType); + } + if (this.references != null) { + foreach (var componentType in allComponents) { + this.references.Add(componentType); + } + } var maxStructSize = 0u; foreach (var item in typeInfos) { if (typeof(IComponent).IsAssignableFrom(item.type) == false) continue; @@ -101,7 +122,14 @@ private void Generate(System.Collections.Generic.List } } - var weightsInfo = GetJobWeightsInfo(jobType); + this.cache.SetKey("JobWeightsInfo"); + WeightsInfo weightsInfo; + if (this.cache.TryGetValue(jobType, out var cachedWeightsInfo) == false) { + weightsInfo = GetJobWeightsInfo(jobType); + this.cache.Add(jobType, weightsInfo); + } else { + weightsInfo = cachedWeightsInfo; + } content.Add($"JobStaticInfo<{jobTypeFullName}>.opsWeight = {weightsInfo.weight}u;"); content.Add($"JobStaticInfo<{jobTypeFullName}>.maxStructSize = {maxStructSize}u;"); @@ -220,7 +248,7 @@ public struct Item { private void AddJobs(ref int uniqueId, System.Text.StringBuilder cacheBuilder, System.Text.StringBuilder funcBuilder, System.Text.StringBuilder structBuilder, System.Text.StringBuilder structUnsafeBuilder, JobType genType) { this.cache.SetKey(typeof(TJobBase).Name); - var jobsComponents = UnityEditor.TypeCache.GetTypesDerivedFrom(typeof(TJobBase)).OrderBy(x => x.FullName).ToList(); + var jobsComponents = CodeGenerator.GetCachedTypesDerivedFrom(typeof(TJobBase)).OrderBy(x => x.FullName).ToList(); CodeGenerator.PatchSystemsList(jobsComponents); foreach (var jobType in jobsComponents) { if (jobType.IsValueType == false) continue; @@ -246,26 +274,30 @@ private void AddJobs(ref int uniqueId, System.Text.StringBuild var tempStructBuilder = new System.Text.StringBuilder(); var tempStructUnsafeBuilder = new System.Text.StringBuilder(); - var jobTypeFullName = EditorUtils.GetTypeName(jobType); + var jobTypeFullName = EditorUtils.GetJobTypeName(jobType); var aspects = new System.Collections.Generic.List(); var components = new System.Collections.Generic.List(); - //var aspectsType = new System.Collections.Generic.HashSet(); - //var componentsType = new System.Collections.Generic.HashSet(); - var interfaces = jobType.GetInterfaces(); + var aspectsType = new System.Collections.Generic.HashSet(); + var componentsType = new System.Collections.Generic.HashSet(); + var interfaces = CodeGenerator.GetCachedInterfaces(jobType); System.Type workInterface = null; foreach (var i in interfaces) { if (i.IsGenericType == true) { foreach (var type in i.GenericTypeArguments) { if (typeof(T0).IsAssignableFrom(type) == true) { if (this.IsValidTypeForAssembly(type) == false) continue; + if (type.IsGenericTypeDefinition) continue; components.Add(EditorUtils.GetDataTypeName(type)); - //componentsType.Add(type); + componentsType.Add(type); + this.references.Add(type); } if (typeof(T1).IsAssignableFrom(type) == true) { if (this.IsValidTypeForAssembly(type) == false) continue; + if (type.IsGenericTypeDefinition) continue; aspects.Add(EditorUtils.GetDataTypeName(type)); - //aspectsType.Add(type); + aspectsType.Add(type); + this.references.Add(type); } } @@ -339,7 +371,12 @@ private void AddJobs(ref int uniqueId, System.Text.StringBuild var i = 0u; var uniqueTypesSorted = uniqueTypes.ToList().OrderBy(x => x.type.FullName); foreach (var typeInfo in uniqueTypesSorted) { - var type = EditorUtils.GetDataTypeName(typeInfo.type); + var componentType = typeInfo.type; + if (componentType.IsGenericTypeDefinition) continue; + if (typeof(IComponent).IsAssignableFrom(componentType) == false) continue; + if (this.IsValidTypeForAssembly(componentType) == false) continue; + this.references.Add(componentType); + var type = EditorUtils.GetDataTypeName(componentType); var RWRO = string.Empty; if (typeInfo.op == RefOp.ReadOnly) RWRO = "RO"; if (typeInfo.op == RefOp.WriteOnly) RWRO = "WO"; @@ -429,7 +466,8 @@ public static System.Collections.Generic.HashSet GetMethodTypesInfo(Me } } - var instructions = body.GetInstructions(); + var instructions = CodeGenerator.GetCachedInstructions(body); + if (instructions == null) continue; foreach (var inst in instructions) { var continueTraverse = true; if (onInstruction?.Invoke(inst) == true) continue; @@ -543,7 +581,10 @@ public static NewEntInfo GetJobEntInfo(System.Type jobType, CustomCodeGenerator if ((member.GetCustomAttribute() != null || visited.Add(new MethodPointerData(member)) == true) && member.GetCustomAttribute() == null) { if (member.GetMethodBody() != null) { - instructions.InsertRange(i + 1, member.GetInstructions()); + var memberInstructions = CodeGenerator.GetCachedInstructions(member); + if (memberInstructions != null) { + instructions.InsertRange(i + 1, memberInstructions); + } } } } @@ -593,6 +634,9 @@ private struct MethodWeightInfo { } public static WeightsInfo GetJobWeightsInfo(System.Type jobType) { + if (CodeGenerator._jobWeightsInfoCache.TryGetValue(jobType, out var cached)) { + return cached; + } var config = new System.Collections.Generic.List(); config.Add(new MethodWeightInfo() { methods = new [] { typeof(Ent).GetMethod(nameof(Ent.NewEnt_INTERNAL), BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public) }, @@ -624,20 +668,30 @@ public static WeightsInfo GetJobWeightsInfo(System.Type jobType) { }); var root = jobType.GetMethod("Execute"); var visited = new System.Collections.Generic.HashSet(); - var instructions = root.GetInstructions().ToList(); + var instructionsList = CodeGenerator.GetCachedInstructions(root); + WeightsInfo weightsResult; + if (instructionsList == null) { + weightsResult = new WeightsInfo { weight = 0 }; + CodeGenerator._jobWeightsInfoCache[jobType] = weightsResult; + return weightsResult; + } + var instructions = instructionsList.ToList(); for (int i = 0; i < instructions.Count; ++i) { var inst = instructions[i]; if (inst.Operand is System.Reflection.MethodInfo member) { if (member.GetCustomAttribute() != null) continue; if ((member.GetCustomAttribute() != null || visited.Add(new MethodPointerData(member)) == true) && member.GetCustomAttribute() == null) { if (member.GetMethodBody() != null) { - instructions.InsertRange(i + 1, member.GetInstructions()); + var memberInstructions = CodeGenerator.GetCachedInstructions(member); + if (memberInstructions != null) { + instructions.InsertRange(i + 1, memberInstructions); + } } } } } - var weight = (uint)jobType.GetInterfaces().Sum(x => x.GenericTypeArguments.Length); + var weight = (uint)CodeGenerator.GetCachedInterfaces(jobType).Sum(x => x.GenericTypeArguments.Length); for (int i = 0; i < instructions.Count; ++i) { var inst = instructions[i]; if (inst.Operand is MethodInfo methodInfo) { @@ -653,9 +707,11 @@ public static WeightsInfo GetJobWeightsInfo(System.Type jobType) { } } - return new WeightsInfo() { + weightsResult = new WeightsInfo() { weight = weight, }; + CodeGenerator._jobWeightsInfoCache[jobType] = weightsResult; + return weightsResult; } public static System.Collections.Generic.HashSet GetJobTypesInfo(System.Type jobType, System.Predicate onInstruction = null) { @@ -688,15 +744,9 @@ public static void UpdateDeps(System.Collections.Generic.HashSet dataList, System.Collections.Generic.List references) { - dataList.Add("#if ENABLE_UNITY_COLLECTIONS_CHECKS && ENABLE_BECS_COLLECTIONS_CHECKS"); - dataList.Add("DebugJobs.InitializeJobsDebug();"); - dataList.Add("#endif"); - } - public override void AddInitialization(System.Collections.Generic.List dataList, System.Collections.Generic.List references) { - - this.GenerateJobsDebug(dataList, references); + this.references = references; + GenerateJobsDebug(dataList, references); this.Generate(dataList, "DoComponents"); this.Generate(dataList, "DoParallelForComponents"); this.Generate(dataList, "DoComponents"); @@ -704,9 +754,13 @@ public override void AddInitialization(System.Collections.Generic.List d this.Generate(dataList, "DoAspect"); this.Generate(dataList, "DoAspectsComponents"); this.Generate(dataList, "DoParallelForAspectsComponents"); - } + private static void GenerateJobsDebug(System.Collections.Generic.List dataList, System.Collections.Generic.List references) { + dataList.Add("#if ENABLE_UNITY_COLLECTIONS_CHECKS && ENABLE_BECS_COLLECTIONS_CHECKS"); + dataList.Add("DebugJobs.InitializeJobsDebug();"); + dataList.Add("#endif"); + } } } \ No newline at end of file diff --git a/Editor/CodeGenerator/Generators/SystemDependenciesCodeGenerator.cs b/Editor/CodeGenerator/Generators/SystemDependenciesCodeGenerator.cs index e0fd9c14..fc4543c8 100644 --- a/Editor/CodeGenerator/Generators/SystemDependenciesCodeGenerator.cs +++ b/Editor/CodeGenerator/Generators/SystemDependenciesCodeGenerator.cs @@ -44,9 +44,13 @@ public override string AddPublicContent() { //UnityEngine.Debug.Log("Processing: " + sys.FullName); var system = sys; - if (system.IsGenericType == true) { - system = system.GetGenericTypeDefinition(); - system = EditorUtils.MakeGenericConstraintType(system); + if (system.IsGenericType == true && system.IsGenericTypeDefinition) { + var concreteType = EditorUtils.MakeGenericConstraintType(system); + if (concreteType != null && !concreteType.IsGenericTypeDefinition) { + system = concreteType; + } else { + continue; + } } if (tempItems.ContainsKey(system) == true) continue; tempItems.Add(system, new Item()); @@ -73,8 +77,17 @@ public override string AddPublicContent() { content.Add($"// system: {system.FullName}"); content.Add("var list = new s::List();"); content.Add("var errors = new s::List();"); - content.Add($"systemDependenciesComponentsGraph.Add(typeof({EditorUtils.GetTypeName(system, showGenericType: false)}), list);"); - content.Add($"systemDependenciesGraphErrors.Add(typeof({EditorUtils.GetTypeName(system, showGenericType: false)}), errors);"); + var systemTypeName = EditorUtils.GetDataTypeName(system); + content.Add($"systemDependenciesComponentsGraph.Add(typeof({systemTypeName}), list);"); + content.Add($"systemDependenciesGraphErrors.Add(typeof({systemTypeName}), errors);"); + if (system.IsGenericType == true && !system.IsGenericTypeDefinition) { + content.Add($"if (systemDependenciesComponentsGraph.ContainsKey(typeof({systemTypeName}).GetGenericTypeDefinition()) == false) {{"); + content.Add($"systemDependenciesComponentsGraph.Add(typeof({systemTypeName}).GetGenericTypeDefinition(), list);"); + content.Add($"}}"); + content.Add($"if (systemDependenciesGraphErrors.ContainsKey(typeof({systemTypeName}).GetGenericTypeDefinition()) == false) {{"); + content.Add($"systemDependenciesGraphErrors.Add(typeof({systemTypeName}).GetGenericTypeDefinition(), errors);"); + content.Add($"}}"); + } { var method = system.GetMethod("OnUpdate"); @@ -272,9 +285,9 @@ public override string AddPublicContent() { str.AppendLine("private static s::Dictionary> systemDependenciesGraph;"); str.AppendLine("private static s::Dictionary> systemDependenciesComponentsGraph;"); str.AppendLine("private static s::Dictionary> systemDependenciesGraphErrors;"); - str.AppendLine("public static s::List GetSystemComponentsDependencies(System.Type type) { InitializeSystemDependenciesInfo(); return systemDependenciesComponentsGraph[type]; }"); - str.AppendLine("public static s::List GetSystemDependenciesErrors(System.Type type) { InitializeSystemDependenciesInfo(); return systemDependenciesGraphErrors[type]; }"); - str.AppendLine("public static s::HashSet GetSystemDependencies(System.Type type) { InitializeSystemDependenciesInfo(); return systemDependenciesGraph[type]; }"); + str.AppendLine("public static s::List GetSystemComponentsDependencies(System.Type type) { InitializeSystemDependenciesInfo(); return systemDependenciesComponentsGraph.TryGetValue(type, out var result) ? result : new s::List(); }"); + str.AppendLine("public static s::List GetSystemDependenciesErrors(System.Type type) { InitializeSystemDependenciesInfo(); return systemDependenciesGraphErrors.TryGetValue(type, out var result) ? result : new s::List(); }"); + str.AppendLine("public static s::HashSet GetSystemDependencies(System.Type type) { InitializeSystemDependenciesInfo(); return systemDependenciesGraph.TryGetValue(type, out var result) ? result : new s::HashSet(); }"); str.AppendLine("public static void InitializeSystemDependenciesInfo() {"); str.AppendLine("if (systemDependenciesGraph != null) return;"); str.AppendLine("systemDependenciesGraph = new s::Dictionary>();"); @@ -315,13 +328,13 @@ public void GetInitializationString(System.Text.StringBuilder str) { str.Append("systemDependenciesGraph.Add("); { str.Append("typeof("); - str.Append(EditorUtils.GetTypeName(this.system, showGenericType: false)); + str.Append(EditorUtils.GetDataTypeName(this.system)); str.Append(")"); } str.Append(","); if (this.dependencies.Count > 0) { str.Append("new s::HashSet() {\ntypeof(" + - string.Join("),\ntypeof(", this.dependencies.Select(x => EditorUtils.GetTypeName(x, showGenericType: false)).ToArray()) + + string.Join("),\ntypeof(", this.dependencies.Select(x => EditorUtils.GetDataTypeName(x)).ToArray()) + ")\n}"); } else { str.Append("null"); @@ -430,20 +443,20 @@ public void AddError(Error error) { } + private static readonly MethodInfo _completeHandleMethod = typeof(Unity.Jobs.JobHandle).GetMethod(nameof(Unity.Jobs.JobHandle.Complete)); + private static readonly MethodInfo _getSystemMethod = typeof(SystemsWorldExt).GetMethod(nameof(SystemsWorldExt.GetSystemPtr)); + private static readonly MethodInfo _withMethod = typeof(ArchetypeQueries.QueryCompose).GetMethod(nameof(ArchetypeQueries.QueryCompose.With)); + private static readonly MethodInfo _withAnyMethod = typeof(ArchetypeQueries.QueryCompose).GetMethod(nameof(ArchetypeQueries.QueryCompose.WithAny)); + private static readonly MethodInfo _withoutMethod = typeof(ArchetypeQueries.QueryCompose).GetMethod(nameof(ArchetypeQueries.QueryCompose.Without)); + private static readonly MethodInfo _withAspectMethod = typeof(ArchetypeQueries.QueryCompose).GetMethod(nameof(ArchetypeQueries.QueryCompose.WithAspect)); + private static readonly MethodInfo _asReadonlyMethod = typeof(QueryBuilder).GetMethod(nameof(QueryBuilder.AsReadonly)); + private MethodInfoDependencies GetDeps(MethodInfo root) { if (root == null) return default; var errors = new System.Collections.Generic.List(); - var completeHandleMethod = typeof(Unity.Jobs.JobHandle).GetMethod(nameof(Unity.Jobs.JobHandle.Complete)); - var getSystemMethod = typeof(SystemsWorldExt).GetMethod(nameof(SystemsWorldExt.GetSystemPtr)); - var withMethod = typeof(ArchetypeQueries.QueryCompose).GetMethod(nameof(ArchetypeQueries.QueryCompose.With)); - var withAnyMethod = typeof(ArchetypeQueries.QueryCompose).GetMethod(nameof(ArchetypeQueries.QueryCompose.WithAny)); - var withoutMethod = typeof(ArchetypeQueries.QueryCompose).GetMethod(nameof(ArchetypeQueries.QueryCompose.Without)); - var withAspectMethod = typeof(ArchetypeQueries.QueryCompose).GetMethod(nameof(ArchetypeQueries.QueryCompose.WithAspect)); - var asReadonlyMethod = typeof(QueryBuilder).GetMethod(nameof(QueryBuilder.AsReadonly)); - var uniqueTypes = new System.Collections.Generic.HashSet(); var q = new System.Collections.Generic.Queue(); q.Enqueue(root); @@ -460,40 +473,39 @@ private MethodInfoDependencies GetDeps(MethodInfo root) { var continueTraverse = true; if (inst.Operand is MethodInfo methodInfo) { if (hasCompleteHandle == false && hasInterestInstructions == false && body == root) { - // search for Complete - if (IsMethod(methodInfo, completeHandleMethod) == true) { + if (IsMethod(methodInfo, _completeHandleMethod) == true) { hasCompleteHandle = true; } } - if (IsMethod(methodInfo, getSystemMethod) == true) { + if (IsMethod(methodInfo, _getSystemMethod) == true) { hasInterestInstructions = true; uniqueTypes.Add(new JobsEarlyInitCodeGenerator.TypeInfo() { type = methodInfo.GetGenericArguments()[0], op = RefOp.ReadWrite, }); continueTraverse = false; - } else if (IsMethod(methodInfo, withMethod) == true) { + } else if (IsMethod(methodInfo, _withMethod) == true) { hasInterestInstructions = true; uniqueTypes.Add(new JobsEarlyInitCodeGenerator.TypeInfo() { type = methodInfo.GetGenericArguments()[0], op = RefOp.ReadOnly, }); continueTraverse = false; - } else if (IsMethod(methodInfo, withAnyMethod) == true) { + } else if (IsMethod(methodInfo, _withAnyMethod) == true) { hasInterestInstructions = true; uniqueTypes.Add(new JobsEarlyInitCodeGenerator.TypeInfo() { type = methodInfo.GetGenericArguments()[0], op = RefOp.ReadOnly, }); continueTraverse = false; - } else if (IsMethod(methodInfo, withoutMethod) == true) { + } else if (IsMethod(methodInfo, _withoutMethod) == true) { hasInterestInstructions = true; uniqueTypes.Add(new JobsEarlyInitCodeGenerator.TypeInfo() { type = methodInfo.GetGenericArguments()[0], op = RefOp.ReadOnly, }); continueTraverse = false; - } else if (IsMethod(methodInfo, withAspectMethod) == true) { + } else if (IsMethod(methodInfo, _withAspectMethod) == true) { hasInterestInstructions = true; var aspect = methodInfo.GetGenericArguments()[0]; var fields = aspect.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); @@ -507,7 +519,7 @@ private MethodInfoDependencies GetDeps(MethodInfo root) { continueTraverse = false; } } - } else if (IsMethod(methodInfo, asReadonlyMethod) == true) { + } else if (IsMethod(methodInfo, _asReadonlyMethod) == true) { hasInterestInstructions = true; isReadonly = true; continueTraverse = false; diff --git a/Editor/CodeGenerator/ScriptsImporter.cs b/Editor/CodeGenerator/ScriptsImporter.cs index f6576adc..547236b4 100644 --- a/Editor/CodeGenerator/ScriptsImporter.cs +++ b/Editor/CodeGenerator/ScriptsImporter.cs @@ -49,6 +49,7 @@ public string GetKey() { private static Data data; private static readonly System.Collections.Generic.Dictionary cache = new System.Collections.Generic.Dictionary(); + private static readonly System.Collections.Generic.Dictionary _typeCache = new System.Collections.Generic.Dictionary(); private static Data LoadData() { var dir = $"{CodeGenerator.ECS}.Cache"; @@ -336,12 +337,16 @@ static int CountChar(string line, char c) { } private static void AddClassName(string ns, string className, string assetPath) { - var type = System.Type.GetType(className); - if (type == null) { - foreach (var asm in System.AppDomain.CurrentDomain.GetAssemblies()) { - type = asm.GetType(className); - if (type != null) break; + System.Type type = null; + if (_typeCache.TryGetValue(className, out type) == false) { + type = System.Type.GetType(className); + if (type == null) { + foreach (var asm in System.AppDomain.CurrentDomain.GetAssemblies()) { + type = asm.GetType(className); + if (type != null) break; + } } + _typeCache[className] = type; } if (data.items == null) data.items = new System.Collections.Generic.List(); var item = new Data.Item() { diff --git a/Editor/Utils/EditorUtils.cs b/Editor/Utils/EditorUtils.cs index e120cbcb..94392215 100644 --- a/Editor/Utils/EditorUtils.cs +++ b/Editor/Utils/EditorUtils.cs @@ -1014,22 +1014,124 @@ public static string UpperFirstLetter(string value) { return $"{value.Substring(0, 1).ToUpper()}{value.Substring(1)}"; } - public static string GetTypeName(System.Type type, bool useFullName = true, bool showGenericType = true) { - var name = type.Name; - if (useFullName == true) { - name = type.FullName; - } - if (type.IsGenericType == true) { - var first = name.Split('[')[0].Replace("+", "."); - if (type.GenericTypeArguments.Length == 0 || showGenericType == false) return $"{first.Replace("`1", "")}<>"; - return $"{first.Replace("`1", $"<{GetTypeName(type.GenericTypeArguments[0], useFullName)}>")}"; - } - return name.Replace("+", ".").Replace("`1", ""); - } - - public static string GetDataTypeName(System.Type type) { - return type.Namespace + "." + type.Name.Replace("+", ".").Replace("`1", ""); - } + public static string GetTypeName(System.Type type, bool useFullName = true, bool showGenericType = true) { + if (type == null) return string.Empty; + if (type.IsGenericParameter) return type.Name; + + string name; + if (type.IsNested) { + var declaringTypeName = GetTypeName(type.DeclaringType, useFullName, showGenericType); + + var nestedName = type.Name; + int backtickIndex = nestedName.LastIndexOf('`'); + if (backtickIndex > 0) { + nestedName = nestedName.Remove(backtickIndex); + } + name = declaringTypeName + "." + nestedName.Replace('+', '.'); + } else { + name = useFullName && !string.IsNullOrEmpty(type.Namespace) + ? type.Namespace + "." + type.Name + : type.Name; + } + + if (type.IsGenericType == true) { + int backtickIndex = name.LastIndexOf('`'); + if (backtickIndex > 0) { + name = name.Remove(backtickIndex); + } + + if (type.IsGenericTypeDefinition) { + return name + (showGenericType ? "<>" : string.Empty); + } + + var genericArgs = type.GetGenericArguments(); + + int declaringTypeArgCount = 0; + if (type.IsNested && type.DeclaringType != null && type.DeclaringType.IsGenericType) { + declaringTypeArgCount = type.DeclaringType.GetGenericArguments().Length; + } + + if (genericArgs.Length > declaringTypeArgCount) { + var args = new System.Collections.Generic.List(); + for (int i = declaringTypeArgCount; i < genericArgs.Length; i++) { + var argName = GetTypeName(genericArgs[i], useFullName, showGenericType); + args.Add(argName); + } + if (args.Count > 0 && showGenericType) { + return name + "<" + string.Join(", ", args) + ">"; + } + } + + return name; + } + + return name.Replace("+", "."); + } + + public static string GetDataTypeName(System.Type type) { + return GetTypeName(type, useFullName: true, showGenericType: true); + } + + public static string GetJobTypeName(System.Type jobType) { + if (!jobType.IsNested || jobType.DeclaringType == null || !jobType.DeclaringType.IsGenericType) { + return GetDataTypeName(jobType); + } + + var declaringType = jobType.DeclaringType; + string declaringTypeName; + + if (declaringType.IsGenericTypeDefinition) { + var baseTypeName = GetTypeName(declaringType, useFullName: true, showGenericType: false); + var fullName = jobType.FullName; + + if (fullName != null && fullName.Contains('[') && fullName.Contains(']')) { + var startBracket = fullName.IndexOf('['); + var endBracket = fullName.LastIndexOf(']'); + if (startBracket > 0 && endBracket > startBracket) { + var bracketContent = fullName.Substring(startBracket + 1, endBracket - startBracket - 1); + var args = System.Text.RegularExpressions.Regex.Matches(bracketContent, @"\[([^\]]+)\]"); + if (args.Count > 0) { + var genericArgs = new System.Collections.Generic.List(); + foreach (System.Text.RegularExpressions.Match match in args) { + var typeNameStr = match.Groups[1].Value.Trim(); + System.Type argType = null; + try { + argType = System.Type.GetType(typeNameStr); + } catch {} + if (argType == null && jobType.Assembly != null) { + try { + argType = jobType.Assembly.GetType(typeNameStr.Split(',')[0].Trim()); + } catch {} + } + if (argType != null) { + genericArgs.Add(GetDataTypeName(argType)); + } + } + if (genericArgs.Count > 0) { + declaringTypeName = $"{baseTypeName}<{string.Join(", ", genericArgs)}>"; + } else { + declaringTypeName = baseTypeName + "<>"; + } + } else { + declaringTypeName = baseTypeName + "<>"; + } + } else { + declaringTypeName = baseTypeName + "<>"; + } + } else { + declaringTypeName = baseTypeName + "<>"; + } + } else { + declaringTypeName = GetDataTypeName(declaringType); + } + + var nestedName = jobType.Name; + int backtickIndex = nestedName.LastIndexOf('`'); + if (backtickIndex > 0) { + nestedName = nestedName.Remove(backtickIndex); + } + return declaringTypeName + "." + nestedName; + } public static string FormatCode(string[] content, int indentSize = 4, int defaultIndent = 2) { @@ -1223,6 +1325,11 @@ public static System.Type MakeGenericConstraintType(System.Type type) { return type.MakeGenericType(GetFirstGenericConstraintType(type)); } + public static string FormatGenericTypeWithSingleArgument(System.Type genericType, System.Type argumentType) { + var baseTypeName = GetTypeName(genericType.GetGenericTypeDefinition(), useFullName: true, showGenericType: false); + return $"{baseTypeName}<{GetTypeName(argumentType)}>"; + } + public static System.Type[] GetTypesDerivedFrom(System.Type genType, System.Type baseTypeWithout) { return GetTypesDerivedFrom(genType, baseTypeWithout.GetInterfaces().Where(x => typeof(IGenericWithout).IsAssignableFrom(x)).ToArray()); } diff --git a/Runtime/Core/Aspects/AspectsStorage.cs b/Runtime/Core/Aspects/AspectsStorage.cs index 5879b5dc..7c266556 100644 --- a/Runtime/Core/Aspects/AspectsStorage.cs +++ b/Runtime/Core/Aspects/AspectsStorage.cs @@ -17,6 +17,9 @@ public unsafe struct AspectDataPtr : IAspectData where T : unmanaged, ICompon public RefRO valueRO; public AspectDataPtr(in World world) { + if (StaticTypes.typeId == 0u) { + StaticTypes.Validate(isTag: false); + } this.value = world.state.ptr->components.GetRW(world.state, world.id); this.valueRO = world.state.ptr->components.GetRO(world.state, world.id); } diff --git a/Tests/ME.BECS.Tests.asmdef b/Tests/ME.BECS.Tests.asmdef index 46bdc6e8..ffd60fc4 100644 --- a/Tests/ME.BECS.Tests.asmdef +++ b/Tests/ME.BECS.Tests.asmdef @@ -9,7 +9,8 @@ "Unity.Collections", "Unity.Mathematics", "Unity.PerformanceTesting", - "ME.BECS.Transforms" + "ME.BECS.Transforms", + "ME.BECS.Editor" ], "includePlatforms": [ "Editor" diff --git a/Tests/Tests.CodeGenerator.Generic.cs b/Tests/Tests.CodeGenerator.Generic.cs new file mode 100644 index 00000000..f5abf12d --- /dev/null +++ b/Tests/Tests.CodeGenerator.Generic.cs @@ -0,0 +1,317 @@ +using System.Linq; +using System.Collections.Generic; +using scg = System.Collections.Generic; +using NUnit.Framework; +using UnityEditor; +using ME.BECS.Editor.Generators; +using ME.BECS.Editor; + +namespace ME.BECS.Tests { + + public interface ITestGenericTag : IComponent { } + + public struct TestGenericTag1 : ITestGenericTag { } + public struct TestGenericTag2 : ITestGenericTag { } + public struct TestGenericTag3 : ITestGenericTag { } + + public struct TestGenericComponent : IComponent where TTag : unmanaged, ITestGenericTag { + public int value; + } + + public struct TestGenericComponentShared : IComponentShared where TTag : unmanaged, ITestGenericTag { + public int value; + } + + public struct TestGenericComponentStatic : IConfigComponentStatic where TTag : unmanaged, ITestGenericTag { + public int value; + } + + public struct TestGenericSystem : IUpdate where TTag : unmanaged, ITestGenericTag { + public void OnUpdate(ref SystemContext context) { + } + } + + public struct TestGenericSystemAwake : IAwake where TTag : unmanaged, ITestGenericTag { + public void OnAwake(ref SystemContext context) { + } + } + + public class Tests_CodeGenerator_Generic { + + [UnityEngine.TestTools.UnitySetUpAttribute] + public System.Collections.IEnumerator SetUp() { + AllTests.Start(); + yield return null; + } + + [UnityEngine.TestTools.UnityTearDownAttribute] + public System.Collections.IEnumerator TearDown() { + AllTests.Dispose(); + yield return null; + } + + [Test] + public void GenericComponentsCodeGenerator_FindsGenericComponentDefinitions() { + var generator = new GenericComponentsCodeGenerator(); + var asms = EditorUtils.GetAssembliesInfo(); + generator.asms = asms; + generator.editorAssembly = true; + + var genericComponentDefinitions = TypeCache.GetTypesDerivedFrom(typeof(IComponent)) + .Where(x => x.IsGenericTypeDefinition && x.IsValueType && typeof(IComponent).IsAssignableFrom(x)) + .ToArray(); + + var testGenericComponent = genericComponentDefinitions.FirstOrDefault(x => + x.Name == "TestGenericComponent`1" && + x.GetGenericArguments().Length == 1); + + Assert.IsNotNull(testGenericComponent, "TestGenericComponent should be found"); + } + + [Test] + public void GenericComponentsCodeGenerator_FindsConstraintType() { + var generator = new GenericComponentsCodeGenerator(); + var asms = EditorUtils.GetAssembliesInfo(); + generator.asms = asms; + generator.editorAssembly = true; + + var genericDef = typeof(TestGenericComponent<>); + var typeParams = genericDef.GetGenericArguments(); + + System.Type constraintType = null; + if (typeParams.Length > 0) { + var allConstraints = typeParams[0].GetGenericParameterConstraints(); + constraintType = allConstraints.FirstOrDefault(x => x.IsInterface == true); + } + + Assert.IsNotNull(constraintType, "Constraint type should be found"); + Assert.AreEqual(typeof(ITestGenericTag), constraintType, "Constraint should be ITestGenericTag"); + } + + [Test] + public void GenericComponentsCodeGenerator_FindsPossibleTypes() { + var generator = new GenericComponentsCodeGenerator(); + var asms = EditorUtils.GetAssembliesInfo(); + generator.asms = asms; + generator.editorAssembly = true; + + var constraintType = typeof(ITestGenericTag); + var possibleTypes = TypeCache.GetTypesDerivedFrom(constraintType) + .Where(x => x.IsValueType && !x.IsGenericTypeDefinition && x.IsVisible) + .ToArray(); + + Assert.IsTrue(possibleTypes.Length >= 3, "Should find at least 3 tag types"); + Assert.IsTrue(possibleTypes.Any(x => x == typeof(TestGenericTag1)), "Should find TestGenericTag1"); + Assert.IsTrue(possibleTypes.Any(x => x == typeof(TestGenericTag2)), "Should find TestGenericTag2"); + Assert.IsTrue(possibleTypes.Any(x => x == typeof(TestGenericTag3)), "Should find TestGenericTag3"); + } + + [Test] + public void GenericComponentsCodeGenerator_CreatesInstantiations() { + var generator = new GenericComponentsCodeGenerator(); + var asms = EditorUtils.GetAssembliesInfo(); + generator.asms = asms; + generator.editorAssembly = true; + + var genericDef = typeof(TestGenericComponent<>); + var constraintType = typeof(ITestGenericTag); + var possibleTypes = TypeCache.GetTypesDerivedFrom(constraintType) + .Where(x => x.IsValueType && !x.IsGenericTypeDefinition && x.IsVisible) + .ToArray(); + + var allInstantiations = new scg::HashSet(); + foreach (var possibleType in possibleTypes) { + try { + var instantiated = genericDef.MakeGenericType(possibleType); + if (typeof(IComponent).IsAssignableFrom(instantiated)) { + allInstantiations.Add(instantiated); + } + } catch { + } + } + + Assert.IsTrue(allInstantiations.Count >= 3, "Should create at least 3 instantiations"); + Assert.IsTrue(allInstantiations.Any(x => x == typeof(TestGenericComponent)), + "Should create TestGenericComponent"); + Assert.IsTrue(allInstantiations.Any(x => x == typeof(TestGenericComponent)), + "Should create TestGenericComponent"); + Assert.IsTrue(allInstantiations.Any(x => x == typeof(TestGenericComponent)), + "Should create TestGenericComponent"); + } + + [Test] + public void GenericComponentsCodeGenerator_GeneratesValidationCalls() { + var generator = new GenericComponentsCodeGenerator(); + var asms = EditorUtils.GetAssembliesInfo(); + generator.dir = "Assets/ME.BECS.Gen/Editor"; + generator.asms = asms; + generator.editorAssembly = true; + + var cache = new Cache(); + var loadMethod = typeof(Cache).GetMethod("Load", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (loadMethod != null) { + loadMethod.Invoke(cache, new object[] { generator.dir, "Cache/GenericComponentsCodeGenerator.cache" }); + } else { + var cacheDataField = typeof(Cache).GetField("cacheData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (cacheDataField != null) { + var cachedItemType = typeof(Cache).GetNestedType("CachedItem", System.Reflection.BindingFlags.Public); + if (cachedItemType != null) { + var dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), cachedItemType); + var dictionary = System.Activator.CreateInstance(dictionaryType); + cacheDataField.SetValue(cache, dictionary); + } + } + } + generator.cache = cache; + + var dataList = new scg::List(); + var references = new scg::List(); + + generator.AddInitialization(dataList, references); + + var allGenericComponentValidations = dataList.Where(x => x.Contains("TestGenericComponent")).ToList(); + var testGenericComponentValidations = dataList.Where(x => + x.Contains("TestGenericComponent") && + (x.Contains("TestGenericTag1") || x.Contains("TestGenericTag2") || x.Contains("TestGenericTag3"))) + .ToList(); + + var genericDefs = TypeCache.GetTypesDerivedFrom(typeof(IComponent)) + .Where(x => x.IsGenericTypeDefinition && x.IsValueType && typeof(IComponent).IsAssignableFrom(x)) + .Where(x => x.Name.Contains("TestGenericComponent")) + .ToArray(); + + var constraintType = typeof(ITestGenericTag); + var possibleTypes = TypeCache.GetTypesDerivedFrom(constraintType) + .Where(x => x.IsValueType && !x.IsGenericTypeDefinition && x.IsVisible) + .Where(x => x.Name.StartsWith("TestGenericTag")) + .ToArray(); + + if (testGenericComponentValidations.Count < 3) { + UnityEngine.Debug.Log($"Found {genericDefs.Length} generic component definitions: {string.Join(", ", genericDefs.Select(x => x.Name))}"); + UnityEngine.Debug.Log($"Found {possibleTypes.Length} possible tag types: {string.Join(", ", possibleTypes.Select(x => x.Name))}"); + UnityEngine.Debug.Log($"Found {allGenericComponentValidations.Count} total TestGenericComponent validations"); + UnityEngine.Debug.Log($"Found {testGenericComponentValidations.Count} TestGenericComponent validations with tags"); + UnityEngine.Debug.Log($"All validations: {string.Join("\n", dataList.Take(20))}"); + } + + Assert.IsTrue(testGenericComponentValidations.Count >= 3, + $"Should generate at least 3 validation calls for TestGenericComponent. Found: {testGenericComponentValidations.Count}, Total TestGenericComponent: {allGenericComponentValidations.Count}, GenericDefs: {genericDefs.Length}, PossibleTypes: {possibleTypes.Length}"); + + foreach (var validation in testGenericComponentValidations) { + Assert.IsTrue(validation.Contains("StaticTypes<"), + "Validation call should use StaticTypes"); + Assert.IsTrue(validation.Contains(".Validate(") || validation.Contains(".ValidateShared(") || validation.Contains(".ValidateStatic("), + "Validation call should use Validate, ValidateShared, or ValidateStatic method"); + } + } + + [Test] + public void GenericComponentsCodeGenerator_HandlesSharedComponents() { + var generator = new GenericComponentsCodeGenerator(); + var asms = EditorUtils.GetAssembliesInfo(); + generator.asms = asms; + generator.editorAssembly = true; + + var genericDef = typeof(TestGenericComponentShared<>); + var constraintType = typeof(ITestGenericTag); + var possibleTypes = TypeCache.GetTypesDerivedFrom(constraintType) + .Where(x => x.IsValueType && !x.IsGenericTypeDefinition && x.IsVisible) + .ToArray(); + + var instantiated = genericDef.MakeGenericType(possibleTypes[0]); + + var isTag = (System.Runtime.InteropServices.Marshal.SizeOf(instantiated) <= 1 && + instantiated.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).Length == 0).ToString().ToLower(); + + var componentTypeName = EditorUtils.GetDataTypeName(instantiated); + var hasCustomHash = instantiated.GetMethod(nameof(IComponentShared.GetHash), System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic) != null || + instantiated.GetInterfaceMap(typeof(IComponentShared)).TargetMethods.Any(m => m.IsPrivate == true && m.Name == typeof(IComponentShared).FullName + "." + nameof(IComponentShared.GetHash)); + var validationCall = $"StaticTypes<{componentTypeName}>.ValidateShared(isTag: {isTag}, hasCustomHash: {hasCustomHash.ToString().ToLower()});"; + + Assert.IsTrue(validationCall.Contains("ValidateShared"), + "Shared component should use ValidateShared"); + } + + [Test] + public void GenericComponentsCodeGenerator_HandlesStaticComponents() { + var generator = new GenericComponentsCodeGenerator(); + var asms = EditorUtils.GetAssembliesInfo(); + generator.asms = asms; + generator.editorAssembly = true; + + var genericDef = typeof(TestGenericComponentStatic<>); + var constraintType = typeof(ITestGenericTag); + var possibleTypes = TypeCache.GetTypesDerivedFrom(constraintType) + .Where(x => x.IsValueType && !x.IsGenericTypeDefinition && x.IsVisible) + .ToArray(); + + var instantiated = genericDef.MakeGenericType(possibleTypes[0]); + + var isTag = (System.Runtime.InteropServices.Marshal.SizeOf(instantiated) <= 1 && + instantiated.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).Length == 0).ToString().ToLower(); + + var componentTypeName = EditorUtils.GetDataTypeName(instantiated); + var validationCall = $"StaticTypes<{componentTypeName}>.ValidateStatic(isTag: {isTag});"; + + Assert.IsTrue(validationCall.Contains("ValidateStatic"), + "Static component should use ValidateStatic"); + } + + [Test] + public void CodeGenerator_PatchSystemsList_FindsGenericSystems() { + var types = new scg::List(); + types.Add(typeof(TestGenericSystem<>)); + types.Add(typeof(TestGenericSystemAwake<>)); + + CodeGenerator.PatchSystemsList(types); + + Assert.IsTrue(types.Any(x => x == typeof(TestGenericSystem)), + "Should find TestGenericSystem"); + Assert.IsTrue(types.Any(x => x == typeof(TestGenericSystem)), + "Should find TestGenericSystem"); + Assert.IsTrue(types.Any(x => x == typeof(TestGenericSystem)), + "Should find TestGenericSystem"); + Assert.IsTrue(types.Any(x => x == typeof(TestGenericSystemAwake)), + "Should find TestGenericSystemAwake"); + + Assert.IsFalse(types.Contains(typeof(TestGenericSystem<>)), + "Should remove generic definition from list"); + Assert.IsFalse(types.Contains(typeof(TestGenericSystemAwake<>)), + "Should remove generic definition from list"); + } + + [Test] + public void CodeGenerator_PatchSystemsList_OnlyOneGenericParameter() { + var types = new scg::List(); + types.Add(typeof(TestGenericSystem<>)); + + CodeGenerator.PatchSystemsList(types); + + var instantiatedTypes = types.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(TestGenericSystem<>)); + + foreach (var instantiatedType in instantiatedTypes) { + var genericArgs = instantiatedType.GetGenericArguments(); + Assert.AreEqual(1, genericArgs.Length, + "Each instantiation should have exactly 1 generic argument"); + } + } + + [Test] + public void CodeGenerator_PatchSystemsList_OnlyValueTypes() { + var types = new scg::List(); + types.Add(typeof(TestGenericSystem<>)); + + CodeGenerator.PatchSystemsList(types); + + var instantiatedTypes = types.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(TestGenericSystem<>)); + + foreach (var instantiatedType in instantiatedTypes) { + Assert.IsTrue(instantiatedType.IsValueType, + "All instantiated types should be value types"); + } + } + + } + +} + diff --git a/Tests/Tests.CodeGenerator.Generic.cs.meta b/Tests/Tests.CodeGenerator.Generic.cs.meta new file mode 100644 index 00000000..3c46f100 --- /dev/null +++ b/Tests/Tests.CodeGenerator.Generic.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 50b65be8b711578429c0cd0fb0178967 \ No newline at end of file