Code Generation - GoldhawkInteractive/X2-Modding GitHub Wiki

Code Generation is activated within Unity using the menu Artitas -> Generate and based on runtime reflection. This requires a project to be in a compiling state before code generation can happen. See CodeGenerationTestcase if you want to execute the generation in the build process or through code.

Features

Entity DSL

  • IComponent DSL: By default, the Artitas Codegeneration framework generates DSL for IComponents which allow you to easily access, add or remove components from an Entity. The following is generated for IComponents:
    • Add{X}(...): An add method that will initialize an IComponent using all its publicly accessible fields and properties. If a specific method is required for initialization, see [Initializer].
    • Remove{X}(): Removes the component from the Entity.
    • {X}(): Accesses the component on the Entity.
  • Special DSL is generated for any IComponent that inherits from the following types:
    • RangeComponent: A component that represents a value constrained to a range. (Minimum, Maximum & Value) The following DSL is generated on the Entity for this:
      • Delta{X}(value): Adds the given value to the Value of the Range on the Entity, this requires that {X} exists on the Entity.
      • Delta{X}Minimum(value), Delta{X}Maximum(value), Delta{X}Range(value): Adds the given value to the Minimum, Maximum or transposes the entire range on the Entity. This requires that {X} exists on the Entity.
    • OneLink<>, ManyLink<>: Represents a relationship between two Entities and needs to be annotated with [LinkedTo] which points to the other side of the relationship:
      • LinkTo{X}(Entity): Links the parameter Entity to this Entity by adding the {X} component to this Entity with the given Entity. The RelationshipManager then handles the other side of the relationship.
      • IsLinkedTo{X}(Entity): Returns whether the parameter Entity is linked to this Entity through {X}.
      • DetachFrom{X}(), DetachFrom{X}(Entity): Will remove the link from this Entity, accepting a parameter Entity from which to detach if this is a ManyLink<>.
    • StaticTag: Represents the association of a single Entity to a Type.
      • TagAs{X}(): Tags the Entity as {X} and removes any previous Entity associated with this tag.
      • UnTagAs{X}(): Removes the {X} tag from the Entity.
      • IsTaggedAs{X}(): Returns whether the Entity is tagged as {X}.
    • StaticGroup: Represents the association of a group of Entities to a Type.
      • Join{X}(): Adds the Entity to the {X} group.
      • Leave{X}(): Removes the Entity from the {X} group.
      • IsMemberOf{X}(): Returns whether the Entity is in the {X} group.

Attributes

Several custom attributes exist in the codegeneration framework which allow you to customize or enhance the code generated.

  • [Archetype]: Annotate any public static readonly Matcher.Builder field with the following will be generated:
    • {X}Attribute: An attribute named after the field, which points at it, which can be used on any Entity type (parameter, etc) to indicate what Entity is expected.
    • Create{X}: An extension factory on Entity that allows you to create an Entity according to the Archetype.
  • [Initializer]: Annotate any method (not constructor) in an IComponent and a Entity.Add{X}method will be generated which accepts these parameters, delegating to that method to initialize the component. Care must be taken that this method properly resets and initializes an IComponent due to the pooling strategy used on Components. Alternatively, you can inherit from IPoolable to define custom release logic when the component is returned to the pool.

Pooling & Optimized Entity operations

To enable the pooling and quick lookup of Components, a specific Entity type needs to be generated. By default, a GlobalWorldConfiguration is generated that will use a specific Entity that has optimized operations on Components for all Component types. To use this optimized World, just instantiate it using GlobalWorldConfiguration. You can define WorldConfigurations for specific sets of Component if so required.

Pooling lifecycle

To hook into the lifecycle of pooling, you can inherit from IPoolable. Any pooling code generated by the code generation will hook up the Component.

Warnings

Unsupported: Generic Parameters

Generic Parameters aren't yet supported on components for which DSL is generated. Generics are fine on components which are abstract, or interface; i.e. those for which no DSL is generated.

Tips & Tricks

Naming: don't strip "useless" suffices (Component, Link, Group)

The codegenerators in Artitas remove any common suffices (Component, Link, Links, Group, Tag) from the name of the Component types. Adding the suffices to the name of components gives benefits such as avoiding clashes with simple data types they might wrap or quickly filtering on groups of types.

Generating RangeComponents

The RangeGenerator is a CodeGenerator that will generate Range Components for you. Annotate any class inheriting from IComponent with the RangeAttribute and give it the names of the RangeComponents that need to be generated. The Range Components will now be generated into the namespace in which the class resides.

Maintenance

When it comes to renaming or deleting components, code may break and result in project state where code generation doesn't work anymore and require that changes be made manually. The following is a list of ways of what might and might not lead to problems, and solutions as to avoid these problems from occurring:

  • Renaming fields: Safe. Using IDE refactoring will not lead to any compilation issues.
  • Changing type of a field: Unsafe, Easy. Apparently, no IDE refactoring is supported for "Type Migration", so you'll manually need to change the type, and where it is used. It might be easier to first remove the component from generation (using [IgnoreForGeneration]), comment out call sites, regenerate and update those call sites.
  • Renaming Components: Unsafe, Easy. Make sure that your IDE refactors existing methods in the rename.
  • Removing/Adding new fields: Unsafe, Easy. This will update the AddXXX() DSL with new parameters and break wherever these methods are called.
  • Moving a Component: Safe. Make sure you use the IDE namespace refactoring/move.
  • Deleting/Removing a Component: Unsafe, Tedious.
    1. Add the [IgnoreForGeneration] attribute to the Component class you want to remove and call Code Generation. This will remove any associated DSL and ID caching from the generated code.
    2. Delete the remaining component usage from your codebase and fix usage where occurring.

FAQ

DSL for RangeComponents generated using the RangeAttribute doesn't show?

Call Generate a second time. The problem is that the Range Components themselves are generated in the first pass. The second pass picks up the generated components and then generates the DSL.

⚠️ **GitHub.com Fallback** ⚠️