public async Task Start(IEnumerable <ModProject> projects) { // TODO: do error checking here. Make sure the last thing references all previous ones. var last = projects.Last(); // Make sure the last one generates code, otherwise it's not needed in the list Debug.Assert(last.ToGenerate, "The last project in list must generate code, otherwise it's pointless"); // Make sure at least one of the projects generates code // Debug.Assert(projects.Any(t => t.ToGenerate), "Nothing generates code"); if (!InitWorkspace()) { return; } _env = new GenerationEnvironment(projects); var lastProject = await msWorkspace.OpenProjectAsync(last.ProjectPath); if (projectLoadFailure) { return; } _env.Init(lastProject.Solution, await lastProject.GetCompilationAsync()); // Now, go through the referenced projects, specified as projects manually, // and analyze their code, perhaps generate is we need to generate code foreach (var project in projects) { Console.WriteLine($"Appending {project.AssemblyName}"); await Generate(project); } }
public IEnumerable <ExportedMethodSymbolWrapper> GetNonNativeExportedMethods(GenerationEnvironment env) { foreach (var method in GetMethods()) { if (method.TryGetExportAttribute(out var attribute)) { env.errorContext.PushThing(method); if (attribute.Chain != null) { var m = new ExportedMethodSymbolWrapper(method, attribute); if (m.TryInit(env)) { yield return(m); } } else { // For now, add this check here but it should probably be elsewhere // Report an error if the class is not a behavior. if (!(this is BehaviorSymbolWrapper)) { env.ReportError($"The class {ClassName} marked a method for export but did not specify the chain path. Note: one may omit the chain path only if the method being exported is inside a behavior class."); } env.errorContext.PopThing(); yield break; } env.errorContext.PopThing(); } } }
private IEnumerable <ExportedMethodSymbolWrapper> GetAllExportedMethods(GenerationEnvironment env) { foreach (var method in GetMethods()) { if (method.TryGetExportAttribute(out var attribute)) { // If the chain string is null, it means that the methods reference the behavior // class they are defined in. // TODO: This actually does have to specify the chain, just without the behavior class part. // Either specify these two separately, as in Chain = "Do", Behavior = "Attackable" // Or split by dot at this point. if (attribute.Chain is null) { var m = new ExportedMethodSymbolWrapper(method, attribute); if (m.TryInit(env, Chains.First())) { yield return(m); } } else { var m = new ExportedMethodSymbolWrapper(method, attribute); if (m.TryInit(env)) { yield return(m); } } } } }
/// <summary> /// This function is independent of the type the Context is defined in. /// This function hashes fields for faster lookups. /// It also caches the necessary fields for convenient iteration. /// </summary> private bool TryHashFields(GenerationEnvironment env) { if (symbol.GetMembers().OfType <IPropertySymbol>().Any(p => p.Name == "actor")) { ActorName = "actor"; } foreach (var s in symbol.GetTypeHierarchy()) { foreach (var field in s.GetInstanceFields()) { fieldsHashed.Add(field.Name, field); if (field.Name == "actor" && field.Type == RelevantSymbols.entity) { ActorName = "actor"; } else if (!field.HasAttribute(RelevantSymbols.OmitAttribute.symbol) && !field.IsImplicitlyDeclared) { if (ActorName is null && field.Type == RelevantSymbols.entity) { ActorName = field.Name; } else { notOmitted.Add(field); } } } }
private bool Init(GenerationEnvironment env) { var uid = exportAttribute.Chain; return(uid.ValidateChainUidAgainst(env) && env.chains.TryGetValue(uid, out var chain) && InitWithChain(env, chain)); }
public static IEnumerable <T> InitAndAfterInit <T>( this IEnumerable <T> guys, GenerationEnvironment context) where T : TypeSymbolWrapperBase { return(guys .OrderBy(g => g.ClassName) .Where(g => g.TryInit(context)) .ToArray() // all the inits must run first // this is the reason we have two functions in the first place .Where(g => g.TryAfterInit(context))); }
protected override bool Init(GenerationEnvironment env) { env.errorContext.ClearFlag(); if (!(base.Init(env))) { return(false); } symbol.TryGetAttribute(RelevantSymbols.AutoActivationAttribute, out var autoActivation); var exportedChains = GetExportedChains(env); if (autoActivation is null) { Chains = exportedChains.ToArray(); if (Chains.Length == 0) { env.ReportError("Behaviors must define at least a chain. Otherwise, they are just components"); } } else { if (exportedChains.Any()) { env.ReportError($"Behaviors decorated with AutoActivation cannot define additional chains."); } else { if (!TryInitActivationContext(env, out var context)) { return(false); } Chains = new IChainWrapper[] { new BehaviorActivationChainWrapper("Check", symbol, context), new BehaviorActivationChainWrapper("Do", symbol, context) }; foreach (var chain in Chains) { env.TryAddChain(chain); } ActivationAlias = autoActivation.Alias; if (env.aliases.Contains(ActivationAlias)) { env.ReportError($"Duplicate alias name {ActivationAlias} in behavior {symbol.Name}."); } } } return(!env.errorContext.Flag); }
protected virtual bool AfterInit(GenerationEnvironment env) { if (exportedMethods == null) { exportedMethods = GetNonNativeExportedMethods(env).ToArray(); } if (contributedChains == null) { contributedChains = GetMoreChainsChains(env).ToArray(); } return(true); }
private IEnumerable <ChainSymbolWrapper> GetExportedChains(GenerationEnvironment env) { foreach (var field in symbol.GetInstanceFields()) { if (field.TryGetAttribute(RelevantSymbols.ChainAttribute, out var chainAttribute)) { var wrapped = new ChainSymbolWrapper(field, chainAttribute); if (wrapped.Init(env) && env.TryAddChain(wrapped)) { yield return(wrapped); } } } }
protected override bool Init(GenerationEnvironment env) { // Workaround: If it is marked as ExportingClass, do after init after all inits. if (symbol.HasAttribute(RelevantSymbols.ExportingClassAttribute.symbol)) { return(base.Init(env) && env.TryAddExportingClass(this)); } // If there are no exported methods, this class is unusable anyway // and should be either given another symbol, or thrown away. return(base.Init(env) && base.AfterInit(env) && (exportedMethods.Length > 0 || contributedChains.Length > 0) && env.TryAddExportingClass(this)); }
public bool Init(GenerationEnvironment env) { // 1. Lookup the type of the context. It is the most nested generic type of the index type. var contextType = (INamedTypeSymbol)symbol.Type.GetLeafTypeArguments().First(); // 2. See if that context has already been cached in the environment. // 3. Initialize the context if it is not found. if (!env.TryGetContextLazy(contextType, out var context)) { return(false); } Context = context; return(true); }
public AliasMethodSymbolWrapper[] GetAliasMethods(GenerationEnvironment env) { // Find aliases var aliasMethods = GetAliasMethodsHelper().ToArray(); // Add alias strings to global aliases foreach (var aliasMethod in aliasMethods) { if (!env.aliases.Add(aliasMethod.Alias)) { env.ReportError($"Aliases must be unique across all types. When processing the {symbol.Name} behavior, found a duplicate alias name: {aliasMethod.Alias}"); } } return(aliasMethods); }
public bool TryInitActivationContext(GenerationEnvironment env, out ContextSymbolWrapper context) { // Initialize the context class symbol wrapper var ctx_symbol = symbol.GetMembers().FirstOrDefault(s => s.Name == "Context"); if (ctx_symbol == null) { env.ReportError($"The {symbol.Name} behavior did not define a nested Context class.\nNote: Any behavior must define a Context class. If you do not have any chains in the behavior, make it a simple component. Behaviors by design differ from components in that they exploit chains."); context = default; return(false); } if (!(ctx_symbol is INamedTypeSymbol named_ctx_symbol)) { env.ReportError($"The Context defined inside {symbol.Name} must be a class"); context = default; return(false); } return(env.TryCacheContext(named_ctx_symbol, out context)); }
public static bool ValidateChainUidAgainst(this string uid, GenerationEnvironment env) { if (!ChainIdentifier.TryParse(uid, env.errorContext, out var parsed)) { return(false); } if (env.exportingClasses.TryGetValue(parsed.Class, out var exportingType)) { switch (parsed.Type) { case ChainContributionType.More: case ChainContributionType.Global: return(TrueOr( exportingType.contributedChains.Any(chain => chain.Name == parsed.Chain && chain.ContributionType == parsed.Type), () => env.ReportError($"{uid} references a non-existent chain: {parsed.Chain}."))); case ChainContributionType.Instance: if (exportingType is BehaviorSymbolWrapper behavior) { if (behavior.Chains.Any(chain => chain.Name == parsed.Chain)) { return(true); } env.ReportError($"{uid} references a non-existent behavior chain: {parsed.Chain}."); } else { env.ReportError($"{uid} referenced an exporting class that was not a behavior: {parsed.Class}. If you meant a static class, use '+{uid}' instead."); } return(false); default: return(false); } } else { env.ReportError($"{uid} references a non-existent exporting class: {parsed.Class}"); } return(false); }
protected override bool Init(GenerationEnvironment env) { if (symbol.HasAttribute(RelevantSymbols.InstanceExportAttribute.symbol)) { env.ReportError($"Components must not have the InstanceExportAttribute since its usage is ambiguous for components."); return(false); } if (base.Init(env)) { flaggedFields = GetFlaggedFields(); aliasMethods = GetAliasMethods(env); injectedFields = GetInjectedFields().ToArray(); HasInitInWorldMethod = symbol.GetMethods().Any(m => !m.IsStatic && m.Name == "InitInWorld"); return(env.TryAddExportingClass(this)); } return(false); }
protected virtual bool Init(GenerationEnvironment env) { usings = GetUsingSyntax(env.Solution); return(true); }
protected override bool AfterInit(GenerationEnvironment env) { return(base.AfterInit(env)); }
public string GetAdapterBody(GenerationEnvironment env) { StringBuilder sb_params = new StringBuilder(); StringBuilder sb_call = new StringBuilder(); // If the method returns bool, it is treated toward propagation. if (SymbolEqualityComparer.Default.Equals(symbol.ReturnType, RelevantSymbols.boolType)) { sb_call.Append("ctx.Propagate = "); } sb_call.Append(GetNamePrefixAtCall()); sb_call.Append($"{Name}("); foreach (var s in symbol.Parameters) { // TODO: allow get properties if (s.Name == Context.ActorName) { sb_call.Append($"ctx.{Context.ActorName}, "); } // If the parameter is of Context type else if (SymbolEqualityComparer.Default.Equals(s.Type, Context.symbol)) { // The parameters need not be appended, since the handlers take ctx by default. sb_call.Append("ctx, "); } // if ctx class has a field of that name and type, reference it directly else if (Context.ContainsFieldWithNameAndType(s.Name, s.Type)) { if (s.RefKind == RefKind.None) { sb_params.AppendLine($"var _{s.Name} = ctx.{s.Name};"); sb_call.Append($"_{s.Name}, "); } else { sb_call.Append($"{s.RefKind.AsKeyword()} ctx.{s.Name}, "); } } // if it is of a component type, retrieve it from the entity else if (s.Type.HasInterface(RelevantSymbols.IComponent)) { // if the name contains the name of an entity type field // of the context followed by an underscore, get the component // from that entity and save it. int indexOf_ = s.Name.IndexOf('_'); bool success = false; if (indexOf_ != -1) { string entity_name = s.Name.Substring(0, indexOf_); if (Context.ContainsEntity(entity_name)) { success = true; sb_params.AppendLine($"var _{s.Name} = ctx.{entity_name}.Get{s.Type.Name}();"); sb_call.Append($"_{s.Name}, "); } else { // TODO: Report warning? } } if (!success) { // get the component from entity. For now, assume that // the entity is assumed to always contain the given component. sb_params.AppendLine($"var _{s.Name} = ctx.actor.Get{s.Type.Name}();"); sb_call.Append($"_{s.Name}, "); } } else { if (SymbolEqualityComparer.Default.Equals(s.Type, RelevantSymbols.entity)) { env.ReportError($"The entity must be named \"actor\", like on the context class"); return(null); } env.ReportError($"The name \"{s.Name}\" is invalid. It does not correspond directly to any of the Context fields and the type of the parameter was not a component type"); return(null); } } if (!symbol.Parameters.IsEmpty) { sb_call.Remove(sb_call.Length - 2, 2); } sb_call.Append(");"); sb_params.Append(sb_call.ToString()); return(sb_params.ToString()); }
public bool TryInit(GenerationEnvironment env, IChainWrapper chain) => env.DoScoped(this, () => InitWithChain(env, chain));
public bool TryAfterInit(GenerationEnvironment env) => env.DoScoped(this, () => AfterInit(env));
// This must be called after all the behaviors have been added to the dictionary // Since this could query them for context and chains. protected override bool AfterInit(GenerationEnvironment env) { exportedMethods = GetAllExportedMethods(env).ToArray(); return(base.AfterInit(env)); }
private bool InitWithChain(GenerationEnvironment env, IChainWrapper chain) { this.ReferencedChain = chain; AdapterBody = GetAdapterBody(env); return(!(AdapterBody is null)); }
public bool TryInit(GenerationEnvironment env) => TryHashFields(env);