Darkyenus/retinazer


An entity-component-system implementation for Java

Download


Step 1. Add the JitPack repository to your build file

Add it in your root settings.gradle at the end of repositories:

	dependencyResolutionManagement {
		repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
		repositories {
			mavenCentral()
			maven { url 'https://jitpack.io' }
		}
	}

Add it in your settings.gradle.kts at the end of repositories:

	dependencyResolutionManagement {
		repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
		repositories {
			mavenCentral()
			maven { url = uri("https://jitpack.io") }
		}
	}

Add to pom.xml

	<repositories>
		<repository>
		    <id>jitpack.io</id>
		    <url>https://jitpack.io</url>
		</repository>
	</repositories>

Add it in your build.sbt at the end of resolvers:

 
    resolvers += "jitpack" at "https://jitpack.io"
        
    

Add it in your project.clj at the end of repositories:

 
    :repositories [["jitpack" "https://jitpack.io"]]
        
    

Step 2. Add the dependency

	dependencies {
		implementation 'com.github.Darkyenus:retinazer:0.3.0'
	}
	dependencies {
		implementation("com.github.Darkyenus:retinazer:0.3.0")
	}
	<dependency>
	    <groupId>com.github.Darkyenus</groupId>
	    <artifactId>retinazer</artifactId>
	    <version>0.3.0</version>
	</dependency>

                            
    libraryDependencies += "com.github.Darkyenus" % "retinazer" % "0.3.0"
        
        

                            
    :dependencies [[com.github.Darkyenus/retinazer "0.3.0"]]
        
        

Readme


Retinazer

License Wemi JitPack JavaDoc

This is an implementation of the entity-component-system design pattern in Java and a fork of the original implementation by Anton Gustafsson. Since forking, the code in this repository has moved in a slightly different direction than the original, so be sure to check it out as well.

Distribution

You can obtain a build through JitPack and treat it like any other Java library. The only dependency is the core jar of libGDX for the primitive collections.

Documentation

All basic building blocks of an ECS are here.

  • Entity is represented by a single int - and entity ID
    • Entity IDs are managed by Engine
    • Entity IDs are given out sequentially, but you can specify ID to use explicitly, for example for multiplayer synchronization
    • Entity IDs are always non-negative (zero is allowed) - passing negative IDs has undefined behavior
  • Components are instances of classes implementing the Component marker (empty) interface
    • Component type is the class implementing the Component interface, so component inheritance is not allowed
    • The most typical way of working with components is to create a new instance per entity, however you can also share single instance among multiple entities
      • Additionally, there is a component pooling support
    • Each entity can have at most one instance of each component type
  • Systems are represented by subclasses of EntitySystem
    • There are different pre-made subclasses to help with common tasks:
    • There is also a generalization of EntitySystem, the EngineService which is useful for doing non-entity updates in certain parts of Engine update, or, as name suggests, to provide some service to other systems

Additionally, there are some concepts specific to this implementation:

  • ComponentSet is an immutable set of component types, which you have to create before you start using the Engine
  • Family describes a set of entities, based on the component types the entity has or does not have
  • Mapper provides access to the components and can be obtained from the Engine
  • EntitySetView is an immutable set of entities. You can obtain an automatically updated set of entities described by a Family through the Engine
  • Wiring is a simple dependency injection system applied through Engine.wire() which fills all variables declared with the @Wire annotation with objects returned by appropriate WireResolver
    • You can use this system to inject whatever you like, but its main purpose is to inject instances of Mappers, EngineServices and even Engine into the registered EngineServices

For more information about the various classes, see the JavaDoc.

Entity lifecycle

The entity management is setup to work in batches, which are triggered by a call to Engine.flush(). This happens at the start of Engine.update() and after each EngineService.update() called within. When you mark entity for removal, it won't be removed until the next flush(). Similarly for component removals. Entity and component additions are instant, but EntitySetView membership update happens during the next flush(). This is done to prevent errors stemming from using recently removed entities and components, to make reasoning about change listeners easier and finally to improve performance by batching changes together.

Additionally, entity ID is guaranteed to not be reused during the very next update cycle. In other words, if your update removes an entity, it is guaranteed that an entity with that ID will not exist during the next update. Only during the update after that can a new entity be assigned that ID.

The ID reuse is important, because it allows the IDs to be small, which helps with performance and memory consumption.

Example

/** A simple component given to entities with position */
class Positioned implements Component {
	public int x, y;
}

/** Singleton component (tag component) given to entities that should fall. */
class Falling implements Component {
	public static final Falling INSTANCE = new Falling();
}

/** A system which moves all entities that should (and can) fall down. */
class GravitySystem extends EntityProcessorSystem {

    // This Mapper will be filled in automatically when the system is added to the engine
	@Wire
	private Mapper<Positioned> positioned;

	public GravitySystem() {
		super(Main.COMPONENT_DOMAIN.familyWith(Positioned.class, Falling.class));
	}

    // This is an EntityProcessorSystem, so this method will be called
    // once per update per entity which belongs to family specified in constructor
	@Override
	protected void process(int entity) {
		final Positioned positioned = this.positioned.get(entity);
		positioned.y -= 1;
	}
}

class Main {
    // A set of all used components
    public static final ComponentSet COMPONENT_DOMAIN = new ComponentSet(Positioned.class, Falling.class);

    public static void main() {
        // To create an Engine, specify the component domain and all systems/services to be used by the engine.
        // The order in which the systems are specified matches the update order.
        final Engine engine = new Engine(COMPONENT_DOMAIN, new GravitySystem()/*, ... */);
        // Instead of wiring, you can always get the Mappers manually
        final Mapper<Positioned> positioned = engine.getMapper(Positioned.class);
        final Mapper<Falling> falling = engine.getMapper(Falling.class);

        final int fallingEntity = engine.createEntity();
        positioned.add(fallingEntity, new Positioned());
        falling.add(fallingEntity, Falling.INSTANCE);

        final int staticEntity = engine.createEntity();
        positioned.add(staticEntity, new Positioned());

        for (int i = 0; i < 10; i++) {
            engine.update();
        }

        assertEquals(0, positioned.get(staticEntity).y);
        assertEquals(-10, positioned.get(fallingEntity).y);
    }
}