[Editor] Put MapObjectsCodeGenerator to work

This commit is contained in:
2021-04-03 12:22:58 +02:00
parent fc96de47c5
commit d3755708d1
8 changed files with 135 additions and 10 deletions

View File

@@ -2,8 +2,7 @@ package com.bartlomiejpluta.base.internal.map;
import com.bartlomiejpluta.base.api.context.Context;
import com.bartlomiejpluta.base.api.map.handler.MapHandler;
import com.bartlomiejpluta.base.api.runner.GameRunner;
public interface MapInitializer {
void run(final Context context, final GameRunner runner, final MapHandler handler);
void run(final Context context, final MapHandler handler);
}

View File

@@ -4,5 +4,5 @@ import com.bartlomiejpluta.base.editor.file.model.FileSystemNode
import java.io.File
interface Compiler {
fun compile(sourceDirectory: FileSystemNode, targetDirectory: File, classPath: Array<File> = emptyArray())
fun compile(sourceDirectories: Array<FileSystemNode>, targetDirectory: File, classPath: Array<File> = emptyArray())
}

View File

@@ -20,14 +20,14 @@ import javax.tools.ToolProvider
class JDKCompiler : Compiler {
private val compiler = ToolProvider.getSystemJavaCompiler()
override fun compile(sourceDirectory: FileSystemNode, targetDirectory: File, classPath: Array<File>) {
override fun compile(sourceDirectories: Array<FileSystemNode>, targetDirectory: File, classPath: Array<File>) {
val classpath = classPath.joinToString(";") { it.absolutePath }
val options = listOf("-g", "-d", targetDirectory.absolutePath, "-classpath", classpath)
val collector = DiagnosticCollector<JavaFileObject>()
val manager = compiler.getStandardFileManager(collector, null, null)
val sources = sourceDirectory.allChildren
val sources = sourceDirectories.flatMap(FileSystemNode::allChildren)
.filter { it.type == FileType.FILE }
.mapNotNull { it as? FileSystemNode }
.map { it.file }

View File

@@ -17,8 +17,8 @@ import java.io.File
class JaninoCompiler : Compiler {
private val compilerFactory = CompilerFactory()
override fun compile(sourceDirectory: FileSystemNode, targetDirectory: File, classPath: Array<File>) = try {
tryToCompile(sourceDirectory, targetDirectory, classPath)
override fun compile(sourceDirectories: Array<FileSystemNode>, targetDirectory: File, classPath: Array<File>) = try {
tryToCompile(sourceDirectories, targetDirectory, classPath)
/* Because Janino parser does not provide error handler for parsers:
*
@@ -38,8 +38,8 @@ class JaninoCompiler : Compiler {
throw BuildException(Severity.ERROR, TAG, location, message, e)
}
private fun tryToCompile(sourceDirectory: FileSystemNode, targetDirectory: File, classPath: Array<File>) {
val compilationUnits = sourceDirectory.allChildren
private fun tryToCompile(sourceDirectories: Array<FileSystemNode>, targetDirectory: File, classPath: Array<File>) {
val compilationUnits = sourceDirectories.flatMap(FileSystemNode::allChildren)
.filter { it.type == FileType.FILE }
.map(::FileNodeResourceAdapter)
.toTypedArray()

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.base.editor.code.build.generator
interface CodeGenerator {
fun generate()
}

View File

@@ -0,0 +1,103 @@
package com.bartlomiejpluta.base.editor.code.build.generator
import com.bartlomiejpluta.base.api.context.Context
import com.bartlomiejpluta.base.api.map.handler.MapHandler
import com.bartlomiejpluta.base.editor.map.asset.GameMapAsset
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.model.map.GameMap
import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import com.bartlomiejpluta.base.editor.project.model.Project
import com.bartlomiejpluta.base.internal.map.MapInitializer
import com.squareup.javapoet.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.lang.String.format
import java.time.Instant
import java.time.format.DateTimeFormatter
import javax.annotation.processing.Generated
import javax.lang.model.element.Modifier
@Component
class MapObjectsCodeGenerator : CodeGenerator {
@Autowired
private lateinit var projectContext: ProjectContext
override fun generate() {
projectContext.project?.let { project ->
project.maps
.map { it to projectContext.loadMap(it.uid) }
.forEach { generateMapObjects(it.first, it.second, project) }
}
}
private fun generateMapObjects(asset: GameMapAsset, map: GameMap, project: Project) {
val runner = className(project.runner)
map.layers
.mapNotNull { it as? ObjectLayer }
.map { generateLayerClass(asset, map, it, runner) }
.forEach { it.writeTo(project.buildGeneratedCodeDirectory) }
}
private fun generateLayerClass(asset: GameMapAsset, map: GameMap, layer: ObjectLayer, runner: ClassName): JavaFile {
val packageName = "com.bartlomiejpluta.base.generated.map"
val className = ClassName.get(
packageName,
format("MapInitializer_%s\$\$Layer%d", map.uid.replace("-", "_"), map.layers.indexOf(layer))
)
val annotation = AnnotationSpec.builder(Generated::class.java)
.addMember("value", "\$S", GENERATOR_NAME)
.addMember("date", "\$S", DateTimeFormatter.ISO_INSTANT.format(Instant.now()))
.addMember("comments", "\$S", "Initializer for map '${asset.name}' (UID: ${asset.uid})")
.build()
val handler = className(map.handler)
val generatedClass = TypeSpec.classBuilder(className)
.addSuperinterface(MapInitializer::class.java)
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
.addAnnotation(annotation)
layer.objects.forEach {
MethodSpec.methodBuilder("_${it.x}x${it.y}")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.addParameter(Context::class.java, "context", Modifier.FINAL)
.addParameter(runner, "runner", Modifier.FINAL)
.addParameter(handler, "handler", Modifier.FINAL)
.addParameter(TypeName.INT, "x", Modifier.FINAL)
.addParameter(TypeName.INT, "y", Modifier.FINAL)
.addCode(it.code)
.build()
.let(generatedClass::addMethod)
}
val runMethod = MethodSpec.methodBuilder("run")
.addAnnotation(Override::class.java)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addParameter(Context::class.java, "context", Modifier.FINAL)
.addParameter(MapHandler::class.java, "handler", Modifier.FINAL)
.addStatement("var customRunner = (\$T) context.getGameRunner()", runner)
.addStatement("var customHandler = (\$T) handler", handler)
layer.objects.forEach {
runMethod.addStatement("_${it.x}x${it.y}(context, customRunner, customHandler, ${it.x}, ${it.y})")
}
generatedClass
.addMethod(runMethod.build())
return JavaFile
.builder(packageName, generatedClass.build())
.build()
}
private fun className(canonical: String) = ClassName.get(
canonical.substringBeforeLast("."),
canonical.substringAfterLast(".")
)
companion object {
private val GENERATOR_NAME = MapObjectsCodeGenerator::class.java.canonicalName
}
}

View File

@@ -4,12 +4,14 @@ import com.bartlomiejpluta.base.editor.code.build.compiler.Compiler
import com.bartlomiejpluta.base.editor.code.build.database.DatabaseAssembler
import com.bartlomiejpluta.base.editor.code.build.exception.BuildException
import com.bartlomiejpluta.base.editor.code.build.game.GameEngineProvider
import com.bartlomiejpluta.base.editor.code.build.generator.CodeGenerator
import com.bartlomiejpluta.base.editor.code.build.packager.JarPackager
import com.bartlomiejpluta.base.editor.code.build.project.ProjectAssembler
import com.bartlomiejpluta.base.editor.code.dependency.DependenciesProvider
import com.bartlomiejpluta.base.editor.common.logs.enumeration.Severity
import com.bartlomiejpluta.base.editor.event.AppendBuildLogsEvent
import com.bartlomiejpluta.base.editor.event.ClearBuildLogsEvent
import com.bartlomiejpluta.base.editor.file.model.FileSystemNode
import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import com.bartlomiejpluta.base.editor.project.model.Project
import javafx.beans.property.SimpleObjectProperty
@@ -24,6 +26,9 @@ class DefaultBuildPipelineService : BuildPipelineService {
@Autowired
private lateinit var dependenciesProvider: DependenciesProvider
@Autowired
private lateinit var generators: List<CodeGenerator>
@Autowired
private lateinit var compiler: Compiler
@@ -92,8 +97,15 @@ class DefaultBuildPipelineService : BuildPipelineService {
eventbus.fire(AppendBuildLogsEvent(Severity.INFO, "Providing compile-time dependencies...", tag = TAG))
val dependencies = dependenciesProvider.provideDependenciesTo(project.buildDependenciesDirectory)
eventbus.fire(AppendBuildLogsEvent(Severity.INFO, "Generating sources...", tag = TAG))
generators.forEach(CodeGenerator::generate)
eventbus.fire(AppendBuildLogsEvent(Severity.INFO, "Compiling sources...", tag = TAG))
compiler.compile(project.codeFSNode, project.buildClassesDirectory, dependencies.toTypedArray())
compiler.compile(
arrayOf(project.codeFSNode, FileSystemNode(project.buildGeneratedCodeDirectory)),
project.buildClassesDirectory,
dependencies.toTypedArray()
)
eventbus.fire(AppendBuildLogsEvent(Severity.INFO, "Assembling game engine...", tag = TAG))
engineProvider.provideBaseGameEngine(outputFile)

View File

@@ -104,6 +104,10 @@ class Project {
var buildOutDirectory by buildOutDirectoryProperty
private set
val buildGeneratedCodeDirectoryProperty = SimpleObjectProperty<File>()
var buildGeneratedCodeDirectory by buildGeneratedCodeDirectoryProperty
private set
val buildDatabaseDumpFileProperty =
createObjectBinding({ File(buildDatabaseDumpDirectory, DATABASE_DUMP_FILE) }, buildDatabaseDumpDirectoryProperty)
val buildDatabaseDumpFile by buildDatabaseDumpFileProperty
@@ -129,6 +133,7 @@ class Project {
buildDirectory = File(it, BUILD_DIR)
buildClassesDirectory = File(it, BUILD_CLASSES_DIR)
buildDependenciesDirectory = File(it, BUILD_DEPENDENCIES_DIR)
buildGeneratedCodeDirectory = File(it, BUILD_GENERATED_DIR)
buildDatabaseDumpDirectory = File(it, BUILD_DATABASE_DUMP_DIR)
buildOutDirectory = File(it, BUILD_OUT_DIR)
}
@@ -176,6 +181,7 @@ class Project {
const val BUILD_CLASSES_DIR = "$BUILD_DIR/classes"
const val BUILD_OUT_DIR = "$BUILD_DIR/out"
const val BUILD_DEPENDENCIES_DIR = "$BUILD_DIR/dependencies"
const val BUILD_GENERATED_DIR = "$BUILD_DIR/generated"
const val BUILD_DATABASE_DUMP_DIR = "$BUILD_DIR/db"
}
}