Skip to content

Latest commit

 

History

History
212 lines (172 loc) · 14.4 KB

File metadata and controls

212 lines (172 loc) · 14.4 KB

gdx-liftoff architecture

THIS IS ALL OUT OF DATE

Large parts of GDX-Liftoff were rewritten in Java in order to use less "behind-the-scenes magic", which seemed impossible to avoid in the existing Autumn+LML-based Kotlin codebase. Avoiding LML allowed more advanced scene2d.ui techniques to be used without hacking up the LML codebase or attempting to make sense of its limited documentation. Parts of this app still use Kotlin, but for the data handling and sample generation primarily, with no code currently using Autumn, Autumn MVC, or LML. Kotlin's actually quite nice for these purposes, and if gdx-liftoff were rewritten in Kotlin today, more tools could be used that weren't available when this project started (like the many KTX libraries). A big part of why the Java rewrite made sense was simply familiarity with Java and a lack of it with Kotlin -- learning how to write basic Kotlin is easy, but learning how to write high-quality, maintainable Kotlin is as hard as it is in any other language, and many of the skills are simply not easily transferred from developing Java to Kotlin.

Also, if you happen to be writing a library for wide usage, and you think a custom language is a great idea, it isn't. If you have millions of dollars to spend on getting documentation authors to document your language, it still isn't. This world has many existing formats, some of which are very extensible, that don't need a Herculean effort to document their various pitfalls and snares to catch the unwary.

Gdx-Liftoff already contains quite a few languages by necessity: Kotlin, Java, Gradle (Groovy), "properties," XML, JSON, shell, batch script, AngelCode BMFont, libGDX .atlas (both new and old formats), MarkDown, ProGuard/R8 configuration, and probably more. Oh yeah, .editorconfig uses TOML.

We don't need more. We don't want more.

OLD ARCHITECTURE DOCS

gdx-liftoff is a copy of czyzby/gdx-setup application, sharing the code general structure and technology stack. The main frameworks used throughout the application include:

  • libGDX.
  • LML: GUI builder. scene2d.ui templates with HTML-like syntax.
  • Autumn: dependency injection framework.
  • Autumn MVC: model-view-controller framework based on Autumn and LML.
  • VisUI: flat design scene2d.ui skin with numerous additional widgets.

Motivation

By today's standards, gdx-liftoff is not a typical Kotlin libGDX application. The tool was started when Kotlin was a relatively new language, and Kotlin extensions for libGDX such as KTX did not exist. The project was a joint effort between @czyzby (the author of the gdx-lml libraries) and @kotcrab (the author of the VisUI framework).

This application was also meant as an example use case of the Autumn MVC framework, which admittedly was not the best tool for the job. Autumn MVC does have some advantages, such as UI hot reload or multi-platform dependency injection with automatic scanning that significantly cuts down on glue code and boilerplate. However, it is a heavily opinionated framework that is rather complex, and does quite a lot under the hood. Additionally, the framework itself was implemented with Java in mind, making many APIs feel clunky in Kotlin.

Using a non-standard technology stack, gdx-liftoff can be difficult to extend for new contributors. This document aims to make it a bit easier by explaining how the application actually works.

Application flow

Initiation

  • main.kt is the main entrypoint of the application. It tries to run a libGDX application, which some additional code to avoid platform-specific issues.
  • AutumnApplication is the implementation of the libGDX ApplicationListener. It handles the lifecycle and rendering of the application, delegating each application event to an internal Autumn MVC service.
  • DesktopClassScanner is used to scan the classpath in search of classes annotated with Autumn component annotations.
    • Application components can be annotated with annotations such as @Component, @Property, @View, @ViewDialog, or @ViewActionContainer.
    • The list of standard Autumn and Autumn MVC annotations is extended with custom application-specific annotations such as @Extension, @ProjectTemplate, @JvmLanguage, and @GdxPlatform.
  • All the annotated classes are found in the classpath during the application startup, constructed with reflection, filled using dependency injection, and registered in the Autumn context as singletons.
    • Autumn injects values into fields annotated with @Inject.
    • Autumn MVC processors handles some fields annotated with annotations such as @Preference or @StageViewport. These are standard Autumn MVC features that are mostly used to configure the Autumn MVC application settings in Configuration.kt.
    • Autumn MVC handles application preferences from the preferences package. They include some values such as package name of the generated project which are cached from the previous tool usage.
    • Custom annotation processors ensure that this step also creates classes representing official and third-party extensions, project templates, languages, and libGDX platforms. A respective annotation is sufficient to register them in the application, and they require no further setup.
  • Component methods annotated with @Initiate are called after the components are constructed.
    • Configuration.initiate loads a custom VisUI skin and extends the LML parser with custom tags representing additional GUI widgets.
  • Application views and dialogs are constructed from LML templates.
    • LmlParser configured in Configuration.kt handles LML templates and associated controllers.
    • LML templates are stored in resources/templates/.
      • Tags within LML templates represent individual Scene2D widgets displayed in the views.
      • Tags starting with : are macros that implement basic scripting functionalities such as iteration or conditionals.
    • Classes annotated with @ViewActionContainer such as GlobalActionContainer define functions available in the LML templates. @LmlAction annotation specifies IDs of the functions as they appear in the templates.
    • Classes annotated with @View and @ViewDialog represent view controllers. They point to their corresponding LML templates. Their fields annotated with @LmlActor and @LmlInject are constructed and injected by the LmlParser after their template is parsed.
      • View methods annotated with @LmlAction can be executed from within LML templates. Notably, their results can also be handled by the parser to display Scene2D widgets. That is how data such as available libraries, platforms, languages, or project templates are exposed to and displayed by the LML templates.
      • Types of fields annotated with @LmlInject can have nested LML annotations such as @LmlActor. They will be handled recursively by the LmlParser.
      • If a value of @LmlInject-annotated field is null, a new instance will be created using reflection. If it already exists, it's annotated field will be handled by the parser, but the instance will not be replaced. That is why some fields can be annotated with both @LmlInject and @Inject - in such cases Autumn will handle dependency injection and LmlParser will process nested fields.
      • LML annotations are strictly related to the UI and mostly handle injection of Scene2D actor instances.
  • MainView is set as the initial (and the only) application view.
    • This view will be continuously rendered by Autumn MVC InterfaceService.
    • Actors defined by the main.lml template are displayed on the application's Scene2D Stage and handle user input.

The results of the initiation are as follows:

  • All annotated application components are created via reflection and filled by a dependency injection framework.
  • All components such as libGDX libraries, platforms, languages, or project templates are created and registered.
  • MainView is constructed from a LML template and rendered.

Project generation

  • Clicking on the non-disabled "Generate project!" button shows the GenerationPrompt dialog.
  • Before the dialog is shown, it launches a task on another thread that handles actual project generation.
  • A Project is created with the current application data.
    • Project data is based on the state of the GUI. The selected libraries, platforms, languages, and template are passed to the project.
  • Project.generate generates the project files.
    • addBasicFiles handles generic files such as .gitignore.
    • addJvmLanguagesSupport configures Java and additional selected JVM languages based on Language classes.
    • addExtensions handles official and third-party extensions defined by Library classes, setting Gradle properties with library versions, and adding appropriate Gradle dependencies to defined modules.
      • This step also fetches the latest extension versions from the corresponding repository such as Maven Central or JitPack.
    • template.apply generates project sources according to the chosen Template.
    • addPlatforms adds platform-specific files of each selected libGDX Platform.
    • addSkinAssets optionally adds Scene2D skin assets to the project.
    • addReadmeFile defines a README file based on the selected project configuration.
    • saveProperties generates a gradle.properties file.
    • saveFiles saves the defined project files in the selected project location.
  • Project.includeGradleWrapper adds the Gradle wrapper files.
  • The prompt dialog buttons are no longer disabled after the project is generated. The dialog can be closed.

Adding content

JVM language support

Add a @JvmLanguage annotated class implementing the Language interface to the languages package. Add the necessary dependencies and files to the project in the initiate function.

Official libGDX extension

Add an @Extension(official = true) annotated class extending the OfficialExtension class to the officialExtensions.kt file. Add library dependencies in the initiate method. Add a Gradle property with the library version if the extension does not share the libGDX version.

The id property defines the unique ID of the library throughout the project. To ensure that the library is properly described in the tool, add id= and idTip= entries to the nls.properties file (replacing the id with a custom one) with the formatted library name and short description. For example:

myLibrary=My Library
myLibraryTip=Generic libGDX utilities.

Third-party extension

Add an @Extension annotated class extending the ThirdPartyExtension class to the thirdPartyExtensions.kt file. Add library dependencies in the initiateDependencies method. Note that the library version based on the chosen id will already be added to gradle.properties, and the addDependency method does not require passing the version. If you need to define a dependency with other version than the library itself, use the addExternalDependency method.

The id property defines the unique ID of the library throughout the project. To ensure that the library is properly described in the tool, add id= and idTip= entries to the nls.properties file (replacing the id with yours) with the formatted library name and short description.

New libGDX backend

Add a @GdxPlatform annotated class implementing the Platform interface along with a class representing the Gradle file that extends GradleFile. Add necessary files in the initiate method. If any platform-specific libraries have to be added to support the backend, modify the necessary extension classes.

See the current backends in the platforms package.

Project template

Add a @ProjectTemplate annotated class implementing the Template interface to the templates package. If the template uses only official libGDX APIs, use @ProjectTemplate(official = true) annotation and put the class in the official package. Otherwise, the template file should be in the unofficial package. Implement the Template interface methods generating all the necessary files. Include any dependencies that are required to run the template. See existing project templates for details.

GUI changes

All GUI changes have to be made in the LML templates and the corresponding view controllers.

See the LML wiki for more info about the framework.

Tool translation

Translate the nls.properties file. Create an issue or submit a pull request with the translation. While the application is capable of supporting different locales, currently only English translation exists.

Additional documentation