Skip to content

Advanced Features

Yaroslav Sarchuk edited this page Aug 29, 2025 · 1 revision

Advanced Features

This guide covers advanced features and techniques for power users of the Atomic Plugin.

๐ŸŽฏ Advanced Code Generation

Custom Code Regions

The plugin generates code with preserve regions for custom additions:

// Generated file structure
public static class EntityExtensions
{
    #region Generated Code
    // Auto-generated methods here
    #endregion
    
    #region Custom Code
    // Your custom additions here - preserved during regeneration
    #endregion
}

Partial Class Integration

Create partial classes to extend generated code:

entityType: "IEntity"
namespace: "Game.Extensions"
className: "EntityExtensions"
# Enable partial class generation

Your custom partial:

namespace Game.Extensions
{
    public static partial class EntityExtensions
    {
        // Custom methods that complement generated ones
        public static void TakeDamage(this IEntity entity, float damage, IEntity source)
        {
            float currentHealth = entity.GetHealth();
            float actualDamage = CalculateDamage(damage, entity, source);
            entity.SetHealth(currentHealth - actualDamage);
            
            if (currentHealth - actualDamage <= 0)
            {
                entity.AddDeadTag();
                OnEntityDeath?.Invoke(entity, source);
            }
        }
        
        public static event Action<IEntity, IEntity> OnEntityDeath;
    }
}

๐Ÿ”„ Multi-File Generation

Generating Multiple Files from One Source

Configure split generation:

entityType: "IEntity"
namespace: "Game.Generated"
className: "EntityExtensions"
directory: "Scripts/Generated"
# Advanced: Split into multiple files
splitFiles: true
filesPerCategory: true

imports:
    System
    UnityEngine

# Each category generates a separate file
tags:
    # Combat tags โ†’ EntityExtensions.Combat.cs
    combat:
        InCombat
        Attacking
        Defending
    
    # Status tags โ†’ EntityExtensions.Status.cs
    status:
        Dead
        Stunned
        Invisible

values:
    # Stats โ†’ EntityExtensions.Stats.cs
    stats:
        Health: int
        Mana: int
        Stamina: int
    
    # Combat โ†’ EntityExtensions.Combat.cs
    combat:
        Damage: float
        CritChance: float
        AttackSpeed: float

๐ŸŽจ Template Customization

Custom Method Templates

Create custom templates for generated methods:

// CustomTemplates.cs
public static class AtomicTemplates
{
    public const string GetterTemplate = @"
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static {Type} Get{Name}(this {EntityType} entity)
        {{
            #if DEBUG
            Debug.Assert(entity != null, ""Entity is null"");
            #endif
            return entity.Get<{Type}>(""{Name}"");
        }}";
    
    public const string SetterTemplate = @"
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void Set{Name}(this {EntityType} entity, {Type} value)
        {{
            #if DEBUG
            Debug.Assert(entity != null, ""Entity is null"");
            ValidateValue(value);
            #endif
            entity.Set(""{Name}"", value);
            OnValueChanged?.Invoke(entity, ""{Name}"", value);
        }}";
}

๐Ÿ” Advanced Search and Navigation

Smart Navigation Features

Go to Declaration

  • From .atomic to C#: Ctrl+Click on type name
  • From C# to .atomic: Ctrl+Click on generated method

Find Usages Across Files

  • Right-click on tag/value in .atomic
  • Select "Find Usages"
  • Shows all generated method usages

Symbol Search

  • Ctrl+Shift+F12 - Search for symbols
  • Includes .atomic defined symbols

Advanced Find Usages

values:
    Health: int  # Right-click โ†’ Find Usages shows:
                 # - GetHealth() calls
                 # - SetHealth() calls
                 # - HasHealth() calls
                 # - RefHealth() calls

๐Ÿ› ๏ธ Build Integration

MSBuild Integration

Add to your .csproj:

<Project>
  <!-- Auto-generate before build -->
  <Target Name="GenerateAtomicFiles" BeforeTargets="BeforeBuild">
    <ItemGroup>
      <AtomicFiles Include="**\*.atomic" />
    </ItemGroup>
    
    <Message Text="Generating from @(AtomicFiles)" Importance="high" />
    
    <!-- Custom MSBuild task -->
    <GenerateAtomic Files="@(AtomicFiles)" />
  </Target>
  
  <!-- Clean generated files -->
  <Target Name="CleanGenerated" BeforeTargets="Clean">
    <Delete Files="**\*.generated.cs" />
  </Target>
</Project>

CI/CD Pipeline Integration

GitHub Actions

name: Build with Atomic Generation

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 6.0.x
    
    - name: Generate Atomic Files
      run: |
        dotnet tool install -g AtomicGenerator
        atomic-gen **/*.atomic
    
    - name: Build
      run: dotnet build
    
    - name: Test
      run: dotnet test

๐Ÿ” Access Control

Generated Method Visibility

Control access levels in generation:

entityType: "IEntity"
namespace: "Game.Internal"
className: "InternalExtensions"
# Access modifiers
accessLevel: "internal"  # public, internal, private

values:
    # Per-value access control
    PublicHealth: int
    internal InternalData: string
    private PrivateCache: object

Generated:

internal static class InternalExtensions
{
    public static int GetPublicHealth(this IEntity entity) { }
    internal static string GetInternalData(this IEntity entity) { }
    private static object GetPrivateCache(this IEntity entity) { }
}

๐Ÿ“Š Analytics and Metrics

Usage Tracking

Track generated method usage:

public static class AtomicMetrics
{
    private static Dictionary<string, int> methodCalls = new();
    
    public static void TrackCall(string methodName)
    {
        #if ANALYTICS_ENABLED
        methodCalls.TryGetValue(methodName, out int count);
        methodCalls[methodName] = count + 1;
        #endif
    }
    
    public static void LogMetrics()
    {
        foreach (var kvp in methodCalls.OrderByDescending(x => x.Value))
        {
            Debug.Log($"{kvp.Key}: {kvp.Value} calls");
        }
    }
}

Integration in generated code:

public static int GetHealth(this IEntity entity)
{
    AtomicMetrics.TrackCall(nameof(GetHealth));
    return entity.Get<int>("Health");
}

๐ŸŽฎ Runtime Code Generation

Dynamic Entity Creation

public class DynamicEntityFactory
{
    private Dictionary<string, Type> dynamicTypes = new();
    
    public IEntity CreateDynamicEntity(string schemaPath)
    {
        // Load .atomic file at runtime
        var schema = LoadAtomicSchema(schemaPath);
        
        // Generate type dynamically
        var entityType = GenerateDynamicType(schema);
        
        // Create instance
        return Activator.CreateInstance(entityType) as IEntity;
    }
    
    private Type GenerateDynamicType(AtomicSchema schema)
    {
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
            new AssemblyName("DynamicEntities"),
            AssemblyBuilderAccess.Run);
            
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
        var typeBuilder = moduleBuilder.DefineType(
            schema.ClassName,
            TypeAttributes.Public | TypeAttributes.Static);
            
        // Generate methods based on schema
        foreach (var value in schema.Values)
        {
            GenerateGetter(typeBuilder, value);
            GenerateSetter(typeBuilder, value);
        }
        
        return typeBuilder.CreateType();
    }
}

๐Ÿ”Œ Plugin Extension Points

Custom Quick Fixes

Create custom quick fixes for .atomic files:

class CustomAtomicQuickFix : LocalQuickFix {
    override fun getName() = "Add Missing Import"
    
    override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
        val element = descriptor.psiElement
        val atomicFile = element.containingFile as? AtomicFile ?: return
        
        // Add import to file
        val import = AtomicPsiFactory.createImport(project, "System.Collections.Generic")
        atomicFile.importsSection?.add(import)
    }
}

Custom Inspections

class UnusedValueInspection : LocalInspectionTool() {
    override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
        return object : AtomicVisitor() {
            override fun visitValueItem(item: AtomicValueItem) {
                val usages = ReferencesSearch.search(item).findAll()
                if (usages.isEmpty()) {
                    holder.registerProblem(
                        item,
                        "Unused value '${item.name}'",
                        ProblemHighlightType.LIKE_UNUSED_SYMBOL,
                        RemoveValueQuickFix()
                    )
                }
            }
        }
    }
}

๐ŸŽฏ Advanced Validation

Custom Validation Rules

public class AtomicValidator
{
    public static void ValidateSchema(AtomicSchema schema)
    {
        // Check for naming conflicts
        var allNames = schema.Tags.Concat(schema.Values.Keys);
        var duplicates = allNames.GroupBy(x => x)
            .Where(g => g.Count() > 1)
            .Select(g => g.Key);
            
        if (duplicates.Any())
        {
            throw new ValidationException($"Duplicate names: {string.Join(", ", duplicates)}");
        }
        
        // Validate type compatibility
        foreach (var value in schema.Values)
        {
            if (!IsValidType(value.Value))
            {
                throw new ValidationException($"Invalid type: {value.Value}");
            }
        }
    }
}

๐Ÿ”„ Migration Tools

Schema Migration

public class AtomicMigration
{
    public void MigrateV1ToV2(string atomicFilePath)
    {
        var content = File.ReadAllText(atomicFilePath);
        var lines = content.Split('\n').ToList();
        
        // Update old syntax to new
        for (int i = 0; i < lines.Count; i++)
        {
            // Old: "value: Health int"
            // New: "Health: int"
            if (lines[i].Contains("value:"))
            {
                var match = Regex.Match(lines[i], @"value:\s+(\w+)\s+(\w+)");
                if (match.Success)
                {
                    lines[i] = $"    {match.Groups[1]}: {match.Groups[2]}";
                }
            }
        }
        
        File.WriteAllLines(atomicFilePath, lines);
    }
}

๐ŸŽจ Code Styling

Custom Formatting Rules

# .atomicformat configuration
formatting:
    indentSize: 4
    indentStyle: space
    maxLineLength: 120
    alignValues: true
    sortImports: true
    sortTags: alphabetical
    sortValues: alphabetical

Result:

imports:
    System
    System.Collections.Generic
    UnityEngine

tags:
    Ally
    Boss
    Enemy
    Player

values:
    Agility:     int
    Damage:      float
    Health:      int
    Intelligence: int
    Strength:    int

๐Ÿ“ Documentation Generation

Auto-generate Documentation

/// <summary>
/// Auto-generated from Character.atomic
/// </summary>
/// <remarks>
/// Tags: Player, Enemy, NPC
/// Values: Health (int), Damage (float), Position (Vector3)
/// </remarks>
public static class CharacterExtensions
{
    /// <summary>
    /// Gets the Health value from the entity.
    /// </summary>
    /// <param name="entity">The entity to get the value from.</param>
    /// <returns>The Health value, or default(int) if not present.</returns>
    public static int GetHealth(this IEntity entity) { }
}

๐Ÿš€ Performance Profiling

Built-in Profiler

[Conditional("PROFILING")]
public static class AtomicProfiler
{
    private static Dictionary<string, ProfileData> profiles = new();
    
    public static void BeginSample(string name)
    {
        profiles[name] = new ProfileData { StartTime = Time.realtimeSinceStartup };
    }
    
    public static void EndSample(string name)
    {
        if (profiles.TryGetValue(name, out var data))
        {
            data.Duration = Time.realtimeSinceStartup - data.StartTime;
            data.CallCount++;
        }
    }
}

๐Ÿ”— Integration with Other Tools

ReSharper Annotations

[PublicAPI]
[UsedImplicitly]
public static class EntityExtensions
{
    [Pure]
    [ContractAnnotation("entity:null => false")]
    public static bool HasHealth([NotNull] this IEntity entity) { }
    
    [ContractAnnotation("entity:null => halt")]
    public static void SetHealth([NotNull] this IEntity entity, int value) { }
}

Source Generators Compatibility

[Generator]
public class AtomicSourceGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // Find all .atomic files
        var atomicFiles = context.AdditionalFiles
            .Where(f => Path.GetExtension(f.Path) == ".atomic");
            
        foreach (var file in atomicFiles)
        {
            var content = file.GetText(context.CancellationToken);
            var generated = GenerateFromAtomic(content);
            context.AddSource($"{Path.GetFileNameWithoutExtension(file.Path)}.g.cs", generated);
        }
    }
}

Next Steps: Explore Performance Optimization or check Known Issues for current limitations.

Navigation

๐Ÿ“š Documentation

๐Ÿ“– Reference

โ“ Help

๐Ÿ”— Links

Clone this wiki locally