diff --git a/README.md b/README.md index 82e486ab0..79022fc55 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Read our [Quickstart guide](https://wiki.mineinabyss.com/geary/quickstart/) to s data class Position(var x: Double, var y: Double) data class Velocity(var x: Double, var y: Double) -fun GearyModule.updatePositionSystem() = system(query()) +fun Geary.updatePositionSystem() = system(query()) .every(interval = 20.milliseconds) .exec { (position, velocity) -> // We can access our components like regular variables! diff --git a/addons/geary-actions/build.gradle.kts b/addons/geary-actions/build.gradle.kts index f57f158e5..959238cd0 100644 --- a/addons/geary-actions/build.gradle.kts +++ b/addons/geary-actions/build.gradle.kts @@ -10,19 +10,16 @@ kotlin { dependencies { implementation(project(":geary-core")) implementation(project(":geary-serialization")) - - implementation(libs.uuid) - implementation(idofrontLibs.idofront.di) } } - val commonTest by getting { + val jvmTest by getting { dependencies { + implementation(project(":geary-test")) implementation(kotlin("test")) implementation(idofrontLibs.kotlinx.coroutines.test) implementation(idofrontLibs.kotlinx.serialization.kaml) implementation(idofrontLibs.kotest.assertions) implementation(idofrontLibs.kotest.property) - implementation(idofrontLibs.idofront.di) implementation(project(":geary-core")) implementation(project(":geary-serialization")) } diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroup.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroup.kt index ab6b2e2dd..b9982965d 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroup.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/ActionGroup.kt @@ -4,12 +4,19 @@ import com.mineinabyss.geary.actions.actions.EmitEventAction import com.mineinabyss.geary.actions.actions.EnsureAction import com.mineinabyss.geary.actions.event_binds.* import com.mineinabyss.geary.actions.expressions.Expression -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.serialization.getWorld import com.mineinabyss.geary.serialization.serializers.InnerSerializer import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import com.mineinabyss.geary.serialization.serializers.SerializedComponents +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.modules.SerializersModule class ActionEntry( val action: Action, @@ -64,7 +71,7 @@ class ActionGroup( customKeys = mapOf( "when" to { ActionWhen.serializer() }, "register" to { ActionRegister.serializer() }, - "onFail" to { ActionOnFail.serializer() }, + "onFail" to { ActionOnFail.Serializer() }, "loop" to { ActionLoop.serializer() } ) ) @@ -72,6 +79,7 @@ class ActionGroup( ), inverseTransform = { TODO() }, transform = { + val world = serializersModule.getWorld() val actions = it.mapNotNull { components -> var action: Action? = null var condition: List? = null @@ -85,11 +93,11 @@ class ActionGroup( comp is ActionRegister -> register = comp.register comp is ActionOnFail -> onFail = comp.action comp is ActionLoop -> loop = - Expression.parseExpression(comp.expression, serializersModule) as Expression> + Expression.parseExpression(world, comp.expression, serializersModule) as Expression> comp is ActionEnvironment -> environment = comp.environment - action != null -> geary.logger.w { "Multiple actions defined in one block!" } - else -> action = EmitEventAction.wrapIfNotAction(comp) + action != null -> Geary.w { "Multiple actions defined in one block!" } + else -> action = EmitEventAction.wrapIfNotAction(world, comp) } } if (action == null) return@mapNotNull null diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/GearyActions.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/GearyActions.kt index 0c7505ca9..d1e059500 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/GearyActions.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/GearyActions.kt @@ -2,18 +2,11 @@ package com.mineinabyss.geary.actions import com.mineinabyss.geary.actions.event_binds.bindEntityObservers import com.mineinabyss.geary.actions.event_binds.parsePassive -import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.addons.dsl.createAddon -class GearyActions { - companion object : GearyAddonWithDefault { - override fun default() = GearyActions() - - override fun GearyActions.install() { - geary.run { - bindEntityObservers() - parsePassive() - } - } +val GearyActions = createAddon("Geary Actions") { + onStart { + bindEntityObservers() + parsePassive() } } diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EmitEventAction.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EmitEventAction.kt index 8ccd52cf8..21d380a30 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EmitEventAction.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EmitEventAction.kt @@ -4,6 +4,7 @@ import com.mineinabyss.geary.actions.Action import com.mineinabyss.geary.actions.ActionGroupContext import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.helpers.componentId +import com.mineinabyss.geary.modules.Geary class EmitEventAction( val eventId: ComponentId, @@ -14,8 +15,8 @@ class EmitEventAction( } companion object { - fun from(data: Any) = EmitEventAction(componentId(data::class), data) + fun from(world: Geary, data: Any) = EmitEventAction(world.componentId(data::class), data) - fun wrapIfNotAction(data: Any) = if (data is Action) data else from(data) + fun wrapIfNotAction(world: Geary, data: Any) = if (data is Action) data else from(world, data) } } diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt index 95a2f895f..5702a0acf 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EnsureAction.kt @@ -2,18 +2,22 @@ package com.mineinabyss.geary.actions.actions import com.mineinabyss.geary.actions.* import com.mineinabyss.geary.helpers.componentId +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.serialization.getWorld import com.mineinabyss.geary.serialization.serializers.InnerSerializer import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import com.mineinabyss.geary.serialization.serializers.SerializedComponents +import kotlinx.serialization.ContextualSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @Serializable(with = EnsureAction.Serializer::class) class EnsureAction( + world: Geary, val conditions: SerializedComponents, ) : Action { @Transient - private val flat = conditions.map { componentId(it::class) to it } + private val flat = conditions.map { world.componentId(it::class) to it } override fun ActionGroupContext.execute() { flat.forEach { (id, data) -> @@ -38,10 +42,10 @@ class EnsureAction( return true } - object Serializer : InnerSerializer( + class Serializer : InnerSerializer( serialName = "geary:ensure", inner = PolymorphicListAsMapSerializer.ofComponents(), inverseTransform = { it.conditions }, - transform = { EnsureAction(it) } + transform = { EnsureAction(serializersModule.getWorld(), it) } ) } diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EvalAction.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EvalAction.kt index f11e0221d..915e1b89a 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EvalAction.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/actions/EvalAction.kt @@ -4,19 +4,21 @@ import com.mineinabyss.geary.actions.Action import com.mineinabyss.geary.actions.ActionGroupContext import com.mineinabyss.geary.actions.expressions.Expression import com.mineinabyss.geary.actions.expressions.InlineExpressionSerializer +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.serialization.serializers.InnerSerializer import kotlinx.serialization.Serializable -@Serializable(with = EvalAction.Serializer::class) class EvalAction( val expression: Expression<*>, ) : Action { override fun ActionGroupContext.execute() = expression.evaluate(this) - object Serializer : InnerSerializer, EvalAction>( + class Serializer( + world: Geary, + ) : InnerSerializer, EvalAction>( serialName = "geary:eval", - inner = InlineExpressionSerializer, + inner = InlineExpressionSerializer(world), inverseTransform = { it.expression }, transform = { EvalAction(it) } ) diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt index e92a9d4ca..3a0e54a48 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt @@ -3,11 +3,14 @@ package com.mineinabyss.geary.actions.event_binds import com.mineinabyss.geary.actions.ActionGroup import com.mineinabyss.geary.actions.actions.EnsureAction import com.mineinabyss.geary.actions.expressions.Expression +import com.mineinabyss.geary.datatypes.ComponentId +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.serialization.serializers.InnerSerializer import com.mineinabyss.geary.serialization.serializers.SerializableComponentId import kotlinx.serialization.Contextual import kotlinx.serialization.ContextualSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.UseContextualSerialization import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer @@ -20,8 +23,8 @@ class EntityObservers( class Serializer : InnerSerializer, EntityObservers>( serialName = "geary:observe", inner = MapSerializer( - SerializableComponentId.serializer(), - ActionGroup.serializer() + ContextualSerializer(ComponentId::class), + ActionGroup.Serializer() ), inverseTransform = { TODO() }, transform = { @@ -67,7 +70,7 @@ value class ActionLoop(val expression: String) class ActionEnvironment(val environment: Map>) { object Serializer : InnerSerializer>, ActionEnvironment>( serialName = "geary:with", - inner = MapSerializer(String.serializer(), Expression.serializer(ContextualSerializer(Any::class))), + inner = MapSerializer(String.serializer(), Expression.Serializer(ContextualSerializer(Any::class))), inverseTransform = ActionEnvironment::environment, transform = { ActionEnvironment(it) } ) diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt index 67870bdf0..b98071710 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt @@ -3,18 +3,18 @@ package com.mineinabyss.geary.actions.event_binds import com.mineinabyss.geary.actions.ActionGroupContext import com.mineinabyss.geary.actions.execute import com.mineinabyss.geary.datatypes.EntityType -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.entity.observe import com.mineinabyss.geary.observers.events.OnSet -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query -fun GearyModule.bindEntityObservers() = observe() +fun Geary.bindEntityObservers() = observe() .involving(query()) .exec { (observers) -> observers.observers.forEach { observer -> val actionGroup = observer.actionGroup - entity.observe(observer.event.id).involving(EntityType(observer.involving.map { it.id })).exec { + entity.observe(observer.event).involving(EntityType(observer.involving)).exec { val context = ActionGroupContext(entity) actionGroup.execute(context) } diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/Passive.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/Passive.kt index 20ded7fd3..2971b2f2c 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/Passive.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/event_binds/Passive.kt @@ -6,12 +6,11 @@ import com.mineinabyss.geary.actions.execute import com.mineinabyss.geary.actions.serializers.DurationSerializer import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.fastForEach -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.events.OnSet import com.mineinabyss.geary.serialization.serializers.InnerSerializer import com.mineinabyss.geary.serialization.serializers.SerializableComponentId -import com.mineinabyss.geary.systems.builders.observe -import com.mineinabyss.geary.systems.builders.system import com.mineinabyss.geary.systems.query.query import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer @@ -37,7 +36,7 @@ class Passive( ) } -fun GearyModule.parsePassive() = observe() +fun Geary.parsePassive() = observe() .involving(query()) .exec { (passive) -> passive.systems.forEach { systemBind -> @@ -45,7 +44,7 @@ fun GearyModule.parsePassive() = observe() entity.add(systemMatchingId) system(query { has(systemMatchingId) - has(systemBind.match.map { it.id }) + has(systemBind.match) }).every(systemBind.every).execOnAll { entities().fastForEach { entity -> runCatching { diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/Expression.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/Expression.kt index 9e37193b3..9c558cd76 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/Expression.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/Expression.kt @@ -1,6 +1,8 @@ package com.mineinabyss.geary.actions.expressions import com.mineinabyss.geary.actions.ActionGroupContext +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.serialization.getWorld import kotlinx.serialization.ContextualSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -31,23 +33,25 @@ sealed interface Expression { } companion object { - fun parseExpression(string: String, module: SerializersModule): Expression<*> { + fun parseExpression( + world: Geary,string: String, module: SerializersModule): Expression<*> { val (name, rem) = getFunctionName(string) val reference = Variable(name) if(rem == "") return reference - return foldFunctions(reference, rem, module) + return foldFunctions(world, reference, rem, module) } tailrec fun foldFunctions( + world: Geary, reference: Expression<*>, remainder: String, module: SerializersModule, ): Expression<*> { val (name, afterName) = getFunctionName(remainder) val (yaml, afterYaml) = getYaml(afterName) - val functionExpr = FunctionExpression.parse(reference, name, yaml, module) + val functionExpr = FunctionExpression.parse(world, reference, name, yaml, module) if (afterYaml == "") return functionExpr - return foldFunctions(functionExpr, afterYaml, module) + return foldFunctions(world, functionExpr, afterYaml, module) } fun getYaml(expr: String): Pair { @@ -76,6 +80,7 @@ sealed interface Expression { override val descriptor: SerialDescriptor = ContextualSerializer(Any::class).descriptor override fun deserialize(decoder: Decoder): Expression { + val world = decoder.serializersModule.getWorld() // Try reading string value, if serial type isn't string, this fails runCatching { decoder.decodeStructure(String.serializer().descriptor) { @@ -84,6 +89,7 @@ sealed interface Expression { }.onSuccess { string -> if (string.startsWith("{{") && string.endsWith("}}")) return parseExpression( + world, string.removePrefix("{{").removeSuffix("}}").trim(), decoder.serializersModule ) as Expression diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpression.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpression.kt index 6aac9fa55..c67db85c6 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpression.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/FunctionExpression.kt @@ -1,19 +1,22 @@ package com.mineinabyss.geary.actions.expressions import com.mineinabyss.geary.actions.ActionGroupContext -import com.mineinabyss.geary.serialization.serializableComponents -import com.mineinabyss.geary.serialization.serializers.SerializableComponentId +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.serialization.SerializableComponents +import com.mineinabyss.geary.serialization.serializers.ComponentIdSerializer import kotlinx.serialization.modules.SerializersModule interface FunctionExpression { companion object { fun parse( + world: Geary, ref: Expression<*>, name: String, yaml: String, module: SerializersModule, ): FunctionExpressionWithInput<*, *> { - val compClass = SerializableComponentId.Serializer.getComponent(name, module) + val serializableComponents = world.getAddon(SerializableComponents) + val compClass = ComponentIdSerializer(serializableComponents.serializers, world).getComponent(name, module) val serializer = serializableComponents.serializers.getSerializerFor(compClass) ?: error("No serializer found for component $name") val expr = diff --git a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/InlineExpressionSerializer.kt b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/InlineExpressionSerializer.kt index 0d2b609a0..7a22998cf 100644 --- a/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/InlineExpressionSerializer.kt +++ b/addons/geary-actions/src/commonMain/kotlin/com/mineinabyss/geary/actions/expressions/InlineExpressionSerializer.kt @@ -1,16 +1,20 @@ package com.mineinabyss.geary.actions.expressions +import com.mineinabyss.geary.modules.Geary import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -object InlineExpressionSerializer : KSerializer> { +class InlineExpressionSerializer( + val world: Geary, +) : KSerializer> { override val descriptor: SerialDescriptor = String.serializer().descriptor override fun deserialize(decoder: Decoder): Expression<*> { return Expression.parseExpression( + world, decoder.decodeString(), decoder.serializersModule ) @@ -19,5 +23,4 @@ object InlineExpressionSerializer : KSerializer> { override fun serialize(encoder: Encoder, value: Expression<*>) { TODO("Not yet implemented") } - } diff --git a/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ConfigEntityObserversTests.kt b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ConfigEntityObserversTests.kt index 24fa165bb..8ce303691 100644 --- a/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ConfigEntityObserversTests.kt +++ b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ConfigEntityObserversTests.kt @@ -3,21 +3,20 @@ package com.mineinabyss.geary.actions import com.mineinabyss.geary.actions.event_binds.EntityObservers import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.serialization.dsl.serialization +import com.mineinabyss.geary.modules.observeWithData +import com.mineinabyss.geary.serialization.SerializableComponents import com.mineinabyss.geary.serialization.dsl.withCommonComponentNames import com.mineinabyss.geary.serialization.formats.YamlFormat -import com.mineinabyss.geary.serialization.serializableComponents +import com.mineinabyss.geary.serialization.serialization import com.mineinabyss.geary.serialization.serializers.GearyEntitySerializer -import com.mineinabyss.geary.systems.builders.observeWithData -import com.mineinabyss.idofront.di.DI +import com.mineinabyss.geary.test.GearyTest import io.kotest.matchers.shouldBe import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class ConfigEntityObserversTests { +class ConfigEntityObserversTests : GearyTest() { @Serializable @SerialName("geary:print") data class Print(val string: String) @@ -26,24 +25,19 @@ class ConfigEntityObserversTests { @SerialName("geary:my_comp") class MyComp() - @BeforeEach - fun createEngine() { - DI.clear() - geary(TestEngineModule) { - install(GearyActions) + override fun setupGeary() = geary(TestEngineModule) { + install(GearyActions) - serialization { - withCommonComponentNames() + serialization { + withCommonComponentNames() - components { - component(String.serializer()) - component(Print.serializer()) - component(EntityObservers.serializer()) - component(MyComp.serializer()) - } + components { + component(String.serializer()) + component(Print.serializer()) + component(EntityObservers.serializer()) + component(MyComp.serializer()) } } - geary.pipeline.runStartupTasks() } @Test @@ -57,10 +51,10 @@ class ConfigEntityObserversTests { string: "Hello World" """.trimIndent() - val format = YamlFormat(serializableComponents.serializers.module) - val entity = format.decodeFromString(GearyEntitySerializer, entityDef) + val format = YamlFormat(getAddon(SerializableComponents).serializers.module) + val entity = format.decodeFromString(GearyEntitySerializer(), entityDef) val printed = mutableListOf() - geary.observeWithData().exec { printed += event.string } + observeWithData().exec { printed += event.string } // act entity.set(MyComp()) diff --git a/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt index 48f32e1a0..581c6572d 100644 --- a/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt +++ b/addons/geary-actions/src/jvmTest/kotlin/com/mineinabyss/geary/actions/ExpressionDecodingTest.kt @@ -1,22 +1,22 @@ package com.mineinabyss.geary.actions -import com.charleskorn.kaml.Yaml import com.mineinabyss.geary.actions.expressions.Expression import com.mineinabyss.geary.actions.expressions.FunctionExpression import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.serialization.dsl.serialization +import com.mineinabyss.geary.serialization.SerializableComponents import com.mineinabyss.geary.serialization.formats.YamlFormat -import com.mineinabyss.idofront.di.DI +import com.mineinabyss.geary.serialization.serialization +import com.mineinabyss.geary.test.GearyTest import io.kotest.matchers.shouldBe import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import org.junit.jupiter.api.Test -class ExpressionDecodingTest { +class ExpressionDecodingTest : GearyTest() { @Serializable data class TestData( val name: Expression, @@ -24,6 +24,16 @@ class ExpressionDecodingTest { val regular: String, ) + override fun setupGeary() = geary(TestEngineModule) { + serialization { + components { + component(TestFunction.serializer()) + } + format("yml", ::YamlFormat) + } + } + + val format get() = getAddon(SerializableComponents).formats["yml"] as YamlFormat // @org.junit.jupiter.api.Test // fun `should correctly decode json`() { // val input = """ @@ -49,7 +59,7 @@ class ExpressionDecodingTest { "regular": "{{ asdf }}" } """.trimIndent() - Yaml.default.decodeFromString(TestData.serializer(), input) shouldBe TestData( + format.decodeFromString(TestData.serializer(), input) shouldBe TestData( name = Expression.Fixed("variable"), age = Expression.Variable("test"), regular = "{{ asdf }}" @@ -66,20 +76,8 @@ class ExpressionDecodingTest { @Test fun shouldCorrectlyParseExpressionFunctions() { - DI.clear() - geary(TestEngineModule){ - serialization { - components { - component(TestFunction.serializer()) - } - format("yml", ::YamlFormat) - } - - } - - geary.pipeline.runStartupTasks() val input = "'{{ entity.geary:testFunction{ string: test } }}'" - val expr = Yaml.default.decodeFromString(Expression.Serializer(String.serializer()), input) + val expr = format.decodeFromString(Expression.Serializer(String.serializer()), input) expr.evaluate(ActionGroupContext(entity = entity())) shouldBe "test" } } diff --git a/addons/geary-autoscan/build.gradle.kts b/addons/geary-autoscan/build.gradle.kts index 9d718d8b4..2a0d84dc2 100644 --- a/addons/geary-autoscan/build.gradle.kts +++ b/addons/geary-autoscan/build.gradle.kts @@ -10,5 +10,4 @@ dependencies { implementation(idofrontLibs.reflections) implementation(idofrontLibs.kotlin.reflect) - implementation(idofrontLibs.idofront.di) } diff --git a/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScanner.kt b/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScanner.kt index 58dd748d1..a06aea036 100644 --- a/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScanner.kt +++ b/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScanner.kt @@ -1,43 +1,24 @@ package com.mineinabyss.geary.autoscan import co.touchlab.kermit.Severity -import com.mineinabyss.geary.addons.GearyPhase -import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.idofront.di.DI +import com.mineinabyss.geary.addons.dsl.createAddon import kotlin.reflect.KClass import kotlin.reflect.KFunction -val autoScanner by DI.observe() - -interface AutoScanner { - val scannedComponents: MutableSet> - val scannedSystems: MutableSet> - - fun installSystems() - - companion object Addon : GearyAddonWithDefault { - override fun default() = object : AutoScanner { - private val logger get() = geary.logger - override val scannedComponents = mutableSetOf>() - override val scannedSystems = mutableSetOf>() - - override fun installSystems() { - scannedSystems.asSequence() - .onEach { it.call(geary) } - .map { it.name } - .let { - if (logger.config.minSeverity <= Severity.Verbose) - logger.i("Autoscan loaded singleton systems: ${it.joinToString()}") - else logger.i("Autoscan loaded ${it.count()} singleton systems") - } +val AutoScanAddon = createAddon("Auto Scan", { AutoScanner() }) { + systems { + configuration.scannedSystems.asSequence() + .onEach { it.call(geary) } + .map { it.name } + .let { + if (geary.logger.config.minSeverity <= Severity.Verbose) + geary.logger.i("Autoscan loaded singleton systems: ${it.joinToString()}") + else geary.logger.i("Autoscan loaded ${it.count()} singleton systems") } - } - - override fun AutoScanner.install() { - geary.pipeline.runOnOrAfter(GearyPhase.INIT_SYSTEMS) { - installSystems() - } - } } } + +class AutoScanner( + val scannedComponents: MutableSet> = mutableSetOf(), + val scannedSystems: MutableSet> = mutableSetOf(), +) diff --git a/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScannerDSL.kt b/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScannerDSL.kt index 1396e497d..e8799e60d 100644 --- a/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScannerDSL.kt +++ b/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScannerDSL.kt @@ -3,10 +3,10 @@ package com.mineinabyss.geary.autoscan import co.touchlab.kermit.Severity import com.mineinabyss.geary.addons.dsl.GearyDSL import com.mineinabyss.geary.datatypes.Component -import com.mineinabyss.geary.modules.GearyConfiguration +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.modules.GearyModule -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.serialization.dsl.serialization +import com.mineinabyss.geary.modules.GearySetup +import com.mineinabyss.geary.serialization.serialization import kotlinx.serialization.* import kotlinx.serialization.modules.polymorphic import org.reflections.Reflections @@ -19,20 +19,19 @@ import kotlin.reflect.jvm.kotlinFunction import kotlin.reflect.typeOf @GearyDSL -fun GearyConfiguration.autoscan( +fun GearySetup.autoscan( classLoader: ClassLoader, vararg limitToPackages: String, - configure: AutoScannerDSL.() -> Unit -) = - install(AutoScanner).also { AutoScannerDSL(classLoader, limitToPackages.toList()).configure() } + configure: AutoScannerDSL.() -> Unit, +) = install(AutoScanAddon) { AutoScannerDSL(this@autoscan, this, classLoader, limitToPackages.toList()).configure() } @GearyDSL class AutoScannerDSL( + private val setup: GearySetup, + private val autoScanner: AutoScanner, private val classLoader: ClassLoader, - private val limitTo: List -) { - private val logger get() = geary.logger - + private val limitTo: List, +) : Geary by setup.geary { private val reflections: Reflections by lazy { Reflections( ConfigurationBuilder() @@ -65,19 +64,16 @@ class AutoScannerDSL( .map { it.kotlin } .filter { !it.hasAnnotation() } .toList() - - geary { - serialization { - components { - scanned.forEach { scannedComponent -> - runCatching { component(scannedComponent) } - .onFailure { - when { - geary.logger.config.minSeverity <= Severity.Verbose -> geary.logger.w("Failed to register component ${scannedComponent.simpleName}\n${it.stackTraceToString()}") - else -> geary.logger.w("Failed to register component ${scannedComponent.simpleName} ${it::class.simpleName}: ${it.message}") - } + setup.serialization { + components { + scanned.forEach { scannedComponent -> + runCatching { component(scannedComponent) } + .onFailure { + when { + logger.config.minSeverity <= Severity.Verbose -> logger.w("Failed to register component ${scannedComponent.simpleName}\n${it.stackTraceToString()}") + else -> logger.w("Failed to register component ${scannedComponent.simpleName} ${it::class.simpleName}: ${it.message}") } - } + } } } } @@ -109,26 +105,24 @@ class AutoScannerDSL( /** Registers a polymorphic serializer for this [kClass], scanning for any subclasses. */ @OptIn(InternalSerializationApi::class) fun subClassesOf(kClass: KClass) { - geary { - serialization { - module { - polymorphic(kClass) { - val scanned = this@AutoScannerDSL.reflections - .get(Scanners.SubTypes.of(kClass.java).asClass>(this@AutoScannerDSL.classLoader)) - .asSequence() - .map { it.kotlin } - .filter { !it.hasAnnotation() } - .filterIsInstance>() - .toList() - - scanned.forEach { scannedClass -> - runCatching { subclass(scannedClass, scannedClass.serializer()) } - .onFailure { this@AutoScannerDSL.logger.w("Failed to load subclass ${scannedClass.simpleName} of ${kClass.simpleName}") } - } - if (geary.logger.config.minSeverity <= Severity.Verbose) - geary.logger.i("Autoscan found subclasses for ${kClass.simpleName}: ${scanned.joinToString { it.simpleName!! }}") - else geary.logger.i("Autoscan found ${scanned.size} subclasses for ${kClass.simpleName}") + setup.serialization { + module { + polymorphic(kClass) { + val scanned = this@AutoScannerDSL.reflections + .get(Scanners.SubTypes.of(kClass.java).asClass>(this@AutoScannerDSL.classLoader)) + .asSequence() + .map { it.kotlin } + .filter { !it.hasAnnotation() } + .filterIsInstance>() + .toList() + + scanned.forEach { scannedClass -> + runCatching { subclass(scannedClass, scannedClass.serializer()) } + .onFailure { this@AutoScannerDSL.logger.w("Failed to load subclass ${scannedClass.simpleName} of ${kClass.simpleName}") } } + if (logger.config.minSeverity <= Severity.Verbose) + logger.i("Autoscan found subclasses for ${kClass.simpleName}: ${scanned.joinToString { it.simpleName!! }}") + else logger.i("Autoscan found ${scanned.size} subclasses for ${kClass.simpleName}") } } } diff --git a/addons/geary-prefabs/build.gradle.kts b/addons/geary-prefabs/build.gradle.kts index 8bb84b091..944ed4c51 100644 --- a/addons/geary-prefabs/build.gradle.kts +++ b/addons/geary-prefabs/build.gradle.kts @@ -10,26 +10,18 @@ kotlin { dependencies { implementation(project(":geary-core")) implementation(project(":geary-serialization")) - - implementation(libs.uuid) - implementation(idofrontLibs.idofront.di) } } - val commonTest by getting { + val jvmTest by getting { dependencies { implementation(kotlin("test")) + implementation(project(":geary-test")) implementation(idofrontLibs.kotlinx.coroutines.test) implementation(idofrontLibs.kotest.assertions) implementation(idofrontLibs.kotest.property) - implementation(idofrontLibs.idofront.di) implementation(project(":geary-core")) implementation(project(":geary-serialization")) - } - } - - val jvmTest by getting { - dependencies { implementation(idofrontLibs.junit.jupiter) } } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabKey.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabKey.kt index f5b0e119d..962645525 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabKey.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabKey.kt @@ -1,6 +1,7 @@ package com.mineinabyss.geary.prefabs import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.prefabs.serializers.PrefabKeySerializer import kotlinx.serialization.Serializable @@ -11,11 +12,6 @@ import kotlinx.serialization.Serializable @Serializable(with = PrefabKeySerializer::class) // We don't make this a value class since calculating substring is pretty expensive compared to one new object instantiation data class PrefabKey private constructor(val namespace: String, val key: String) { - fun toEntity(): Entity = toEntityOrNull() - ?: error("Requested non null prefab entity for $this, but it does not exist.") - - fun toEntityOrNull(): Entity? = prefabs.manager[this] - val full get() = "$namespace:$key" override fun toString(): String = full @@ -37,3 +33,7 @@ data class PrefabKey private constructor(val namespace: String, val key: String) } } +fun Geary.entityOfOrNull(key: PrefabKey?): Entity? = key?.let { getAddon(Prefabs).manager[key] } + +fun Geary.entityOf(key: PrefabKey): Entity = entityOfOrNull(key) + ?: error("Requested non null prefab entity for $this, but it does not exist.") diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabLoader.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabLoader.kt index 9b4abda0b..88a1af7b8 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabLoader.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabLoader.kt @@ -1,39 +1,39 @@ package com.mineinabyss.geary.prefabs -import com.benasher44.uuid.Uuid +import co.touchlab.kermit.Logger import com.mineinabyss.geary.components.relations.NoInherit import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.fastForEach -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances import com.mineinabyss.geary.prefabs.configuration.components.InheritPrefabs import com.mineinabyss.geary.prefabs.configuration.components.Prefab import com.mineinabyss.geary.prefabs.helpers.inheritPrefabsIfNeeded import com.mineinabyss.geary.serialization.formats.Format.ConfigType.NON_STRICT -import com.mineinabyss.geary.serialization.serializableComponents +import com.mineinabyss.geary.serialization.formats.Formats import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer.Companion.provideConfig -import com.mineinabyss.geary.systems.builders.cache import com.mineinabyss.geary.systems.query.Query import kotlinx.serialization.Serializable import kotlinx.serialization.modules.SerializersModule import okio.Path +import kotlin.uuid.Uuid -class PrefabLoader { - private val formats get() = serializableComponents.formats - - private val logger get() = geary.logger - +class PrefabLoader( + val world: Geary, + val formats: Formats, + val logger: Logger, +) { private val readFiles = mutableListOf() - private val needsInherit = geary.cache(NeedsInherit()) + private val needsInherit = world.cache(::NeedsInherit) fun addSource(path: PrefabPath) { readFiles.add(path) } - class NeedsInherit : Query() { + class NeedsInherit(world: Geary) : Query(world) { val inheritPrefabs by get() } @@ -113,7 +113,7 @@ class PrefabLoader { return PrefabLoadResult.Failure(exception) } - val entity = writeTo ?: entity() + val entity = writeTo ?: world.entity() entity.addRelation() entity.addRelation() entity.addRelation() @@ -128,7 +128,7 @@ class PrefabLoader { fun loadFromPathOrReloadExisting(namespace: String, path: Path): PrefabLoadResult { val key = PrefabKey.of(namespace, path.name.substringBeforeLast('.')) - val existing = prefabs.manager[key] + val existing = world.getAddon(Prefabs).manager[key] existing?.clear() return loadFromPath(namespace, path, existing) } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt deleted file mode 100644 index aa4310d08..000000000 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.mineinabyss.geary.prefabs - -import com.mineinabyss.geary.addons.GearyPhase -import com.mineinabyss.geary.addons.Namespaced -import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault -import com.mineinabyss.geary.addons.dsl.GearyDSL -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.prefabs.configuration.systems.* -import com.mineinabyss.geary.prefabs.systems.createInheritPrefabsOnLoadListener -import com.mineinabyss.geary.prefabs.systems.createTrackPrefabsByKeyListener -import com.mineinabyss.idofront.di.DI - -val prefabs by DI.observe() - -interface Prefabs { - val manager: PrefabManager - val loader: PrefabLoader - - companion object : GearyAddonWithDefault { - override fun default() = object : Prefabs { - override val manager = PrefabManager() - override val loader: PrefabLoader = PrefabLoader() - } - - - override fun Prefabs.install() { - geary.run { - createInheritPrefabsOnLoadListener() - createParseChildOnPrefabListener() - createParseChildrenOnPrefabListener() - createParseInstancesOnPrefabListener() - createParseRelationOnPrefabListener() - createParseRelationWithDataListener() - createTrackPrefabsByKeyListener() - createCopyToInstancesSystem() - reEmitEvent() - } - geary.pipeline.runOnOrAfter(GearyPhase.INIT_ENTITIES) { - loader.loadOrUpdatePrefabs() - } - } - } -} - -@GearyDSL -fun Namespaced.prefabs(configure: PrefabsDSL.() -> Unit) = - gearyConf.install(Prefabs).also { PrefabsDSL(this).configure() } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabsDSL.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabsDSL.kt index d183d0a08..2be146961 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabsDSL.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabsDSL.kt @@ -2,24 +2,25 @@ package com.mineinabyss.geary.prefabs import com.mineinabyss.geary.addons.Namespaced import com.mineinabyss.geary.addons.dsl.GearyDSL -import com.mineinabyss.geary.serialization.fileSystem +import okio.FileSystem import okio.Path @GearyDSL class PrefabsDSL( - private val namespaced: Namespaced + private val prefabsBuilder: PrefabsBuilder, + private val fileSystem: FileSystem, + private val namespaced: Namespaced, ) { - private val loader = prefabs.loader /** Loads prefab entities from all files inside a [directory][from], into a given [namespace] */ fun from( vararg from: Path, ) { - loader.addSource(PrefabPath(namespaced.namespace) { from.asSequence() }) + prefabsBuilder.paths.add(PrefabPath(namespaced.namespace) { from.asSequence() }) } fun fromRecursive(folder: Path) { - loader.addSource(PrefabPath(namespaced.namespace) { + prefabsBuilder.paths.add(PrefabPath(namespaced.namespace) { fileSystem .listRecursively(folder, true) .filter { it.name.contains('.') } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabsModule.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabsModule.kt new file mode 100644 index 000000000..c88de9b56 --- /dev/null +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/PrefabsModule.kt @@ -0,0 +1,52 @@ +package com.mineinabyss.geary.prefabs + +import com.mineinabyss.geary.addons.Namespaced +import com.mineinabyss.geary.addons.dsl.Addon +import com.mineinabyss.geary.addons.dsl.GearyDSL +import com.mineinabyss.geary.addons.dsl.createAddon +import com.mineinabyss.geary.prefabs.configuration.systems.* +import com.mineinabyss.geary.prefabs.systems.createInheritPrefabsOnLoadListener +import com.mineinabyss.geary.prefabs.systems.createTrackPrefabsByKeyListener +import com.mineinabyss.geary.serialization.FileSystem +import com.mineinabyss.geary.serialization.SerializableComponents + +interface PrefabsModule { + val manager: PrefabManager + val loader: PrefabLoader +} + +class PrefabsBuilder { + val paths: MutableList = mutableListOf() +} + +val Prefabs + get() = createAddon("Prefabs", { PrefabsBuilder() }) { + val formats = geary.getAddon(SerializableComponents).formats + val module = object : PrefabsModule { + override val manager = PrefabManager() + override val loader: PrefabLoader = PrefabLoader(geary, formats, logger) + } + + configuration.paths.forEach { module.loader.addSource(it) } + + systems { + createInheritPrefabsOnLoadListener() + createParseChildOnPrefabListener() + createParseChildrenOnPrefabListener() + createParseInstancesOnPrefabListener() + createParseRelationOnPrefabListener() + createParseRelationWithDataListener() + createTrackPrefabsByKeyListener() + createCopyToInstancesSystem() + reEmitEvent() + } + + entities { + module.loader.loadOrUpdatePrefabs() + } + module + } + +@GearyDSL +fun Namespaced.prefabs(configure: PrefabsDSL.() -> Unit): Addon = + setup.install(Prefabs) { PrefabsDSL(this, setup.geary.getConfiguration(FileSystem), this@prefabs).configure() } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/CopyToInstances.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/CopyToInstances.kt index e5e7d49f5..0c6092b2a 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/CopyToInstances.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/CopyToInstances.kt @@ -2,9 +2,11 @@ package com.mineinabyss.geary.prefabs.configuration.components import com.mineinabyss.geary.datatypes.Component import com.mineinabyss.geary.datatypes.Entity -import com.mineinabyss.geary.serialization.serializableComponents +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.serialization.SerializableComponents import com.mineinabyss.geary.serialization.serializers.SerializedComponents import com.mineinabyss.geary.serialization.setAllPersisting +import kotlinx.serialization.Contextual import kotlinx.serialization.Polymorphic import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -21,6 +23,7 @@ import kotlinx.serialization.Serializable data class CopyToInstances( private val temporary: SerializedComponents? = null, private val persisting: SerializedComponents? = null, + private val world: @Contextual Geary, ) { @Serializable private data class DeepCopy( @@ -28,7 +31,7 @@ data class CopyToInstances( val persisting: List<@Polymorphic Component>? ) - val formats get() = serializableComponents.formats + val formats get() = world.getAddon(SerializableComponents).formats // This is the safest and cleanest way to deep-copy, even if a little performance intense. private val serializedComponents by lazy { diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt index 159db7c42..03147aef7 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt @@ -1,12 +1,12 @@ package com.mineinabyss.geary.prefabs.configuration.systems -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observeWithData import com.mineinabyss.geary.observers.events.OnExtend import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances -import com.mineinabyss.geary.systems.builders.observeWithData -fun GearyModule.createCopyToInstancesSystem() = observeWithData() +fun Geary.createCopyToInstancesSystem() = observeWithData() .exec { - val copy = event.baseEntity.get() ?: return@exec + val copy = event.baseEntity.toGeary().get() ?: return@exec copy.decodeComponentsTo(entity) } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseChildOnPrefab.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseChildOnPrefab.kt index cb0ded686..135888b25 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseChildOnPrefab.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseChildOnPrefab.kt @@ -4,16 +4,16 @@ import com.mineinabyss.geary.components.EntityName import com.mineinabyss.geary.components.relations.NoInherit import com.mineinabyss.geary.helpers.addParent import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.events.OnSet import com.mineinabyss.geary.prefabs.configuration.components.ChildOnPrefab import com.mineinabyss.geary.prefabs.configuration.components.ChildrenOnPrefab import com.mineinabyss.geary.prefabs.configuration.components.Prefab -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query -fun GearyModule.createParseChildOnPrefabListener() = observe() +fun Geary.createParseChildOnPrefabListener() = observe() .involving(query()) .exec { (child) -> entity { @@ -23,7 +23,7 @@ fun GearyModule.createParseChildOnPrefabListener() = observe() entity.remove() } -fun GearyModule.createParseChildrenOnPrefabListener() = observe() +fun Geary.createParseChildrenOnPrefabListener() = observe() .involving(query()) .exec { (children) -> children.nameToComponents.forEach { (name, components) -> diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseInstancesOnPrefab.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseInstancesOnPrefab.kt index 1ffc330db..5349edcb9 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseInstancesOnPrefab.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseInstancesOnPrefab.kt @@ -2,16 +2,16 @@ package com.mineinabyss.geary.prefabs.configuration.systems import com.mineinabyss.geary.components.relations.NoInherit import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.events.OnSet import com.mineinabyss.geary.prefabs.PrefabKey import com.mineinabyss.geary.prefabs.configuration.components.InheritPrefabs import com.mineinabyss.geary.prefabs.configuration.components.InstancesOnPrefab import com.mineinabyss.geary.prefabs.configuration.components.Prefab -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query -fun GearyModule.createParseInstancesOnPrefabListener() = observe() +fun Geary.createParseInstancesOnPrefabListener() = observe() .involving(query()).exec { (instances, prefabKey) -> entity.addRelation() instances.nameToComponents.forEach { (name, components) -> diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseReEmitEvent.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseReEmitEvent.kt index ea4f5fc26..7bcabb644 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseReEmitEvent.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseReEmitEvent.kt @@ -1,12 +1,11 @@ package com.mineinabyss.geary.prefabs.configuration.systems -import com.mineinabyss.geary.helpers.toGeary -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observeWithData import com.mineinabyss.geary.prefabs.configuration.components.ReEmitEvent -import com.mineinabyss.geary.systems.builders.observeWithData -fun GearyModule.reEmitEvent() = observeWithData().exec { - entity.getRelationsByKind(event.findByRelationKind.id).forEach { relation -> +fun Geary.reEmitEvent() = observeWithData().exec { + entity.getRelationsByKind(event.findByRelationKind).forEach { relation -> val entity = relation.target.toGeary() if (entity.exists()) entity.emit(event = event.dataComponentId, data = event.data) } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationOnPrefab.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationOnPrefab.kt index 43c8bf7d5..d4ed99faa 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationOnPrefab.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationOnPrefab.kt @@ -1,13 +1,13 @@ package com.mineinabyss.geary.prefabs.configuration.systems import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.events.OnSet import com.mineinabyss.geary.prefabs.configuration.components.RelationOnPrefab -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query -fun GearyModule.createParseRelationOnPrefabListener() = observe() +fun Geary.createParseRelationOnPrefabListener() = observe() .involving(query()).exec { (relation) -> try { val target = entity.lookup(relation.target)?.id ?: return@exec diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationWithDataSystem.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationWithDataSystem.kt index 9903ec9a4..9e2d438a1 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationWithDataSystem.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationWithDataSystem.kt @@ -1,18 +1,18 @@ package com.mineinabyss.geary.prefabs.configuration.systems -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.events.OnSet import com.mineinabyss.geary.systems.accessors.RelationWithData -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query -fun GearyModule.createParseRelationWithDataListener() = observe() +fun Geary.createParseRelationWithDataListener() = observe() .involving(query>()).exec { (relationWithData) -> val entity = entity val data = relationWithData.data val targetData = relationWithData.targetData if (data != null) entity.set(data, relationWithData.relation.id) else entity.add(relationWithData.relation.id) - if (targetData != null) entity.set(targetData, relationWithData.target.id) + if (targetData != null) entity.set(targetData, relationWithData.target) entity.remove>() } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/helpers/InheritPrefabs.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/helpers/InheritPrefabs.kt index 85988c2a7..89616dc6f 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/helpers/InheritPrefabs.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/helpers/InheritPrefabs.kt @@ -4,22 +4,23 @@ import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.prefabs.PrefabKey import com.mineinabyss.geary.prefabs.configuration.components.InheritPrefabs +import com.mineinabyss.geary.prefabs.entityOfOrNull /** * Adds prefabs to this entity from an [InheritPrefabs] component. Will make sure parents have their prefabs * added from this component before trying to add it */ -fun Entity.inheritPrefabsIfNeeded(instances: Set = setOf()) { - if (this in instances) +fun Entity.inheritPrefabsIfNeeded(instances: Set = setOf()): Unit = with(world) { + if (this@inheritPrefabsIfNeeded in instances) error("Circular dependency found while loading prefabs for ${get()}, chain was: $instances") val add = get() ?: return remove() add.from.mapNotNull { key -> - key.toEntityOrNull().also { - if (it == null) geary.logger.w("Prefab ${get()} could not inherit prefab $key, it does not exist") + entityOfOrNull(key).also { + if (it == null) logger.w("Prefab ${get()} could not inherit prefab $key, it does not exist") } }.forEach { parent -> - parent.inheritPrefabsIfNeeded(instances + this) + parent.inheritPrefabsIfNeeded(instances + this@inheritPrefabsIfNeeded) extend(parent) } } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/serializers/PrefabByReferenceSerializer.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/serializers/PrefabByReferenceSerializer.kt deleted file mode 100644 index e4c4271c1..000000000 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/serializers/PrefabByReferenceSerializer.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.mineinabyss.geary.prefabs.serializers - -import com.mineinabyss.geary.datatypes.Entity -import com.mineinabyss.geary.prefabs.PrefabKey -import com.mineinabyss.geary.prefabs.PrefabManager -import com.mineinabyss.geary.prefabs.prefabs -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder - -/** - * Allows us to serialize entity types to a reference to ones actually registered in the system. - * This is used to load the static entity type when we decode components from an in-game entity. - */ -@Deprecated("This will not work properly until ktx.serialization fully supports inline classes") -class PrefabByReferenceSerializer : KSerializer { - private val prefabManager: PrefabManager get() = prefabs.manager - - override val descriptor = SerialDescriptor("geary:prefab", PrefabKey.serializer().descriptor) - - override fun deserialize(decoder: Decoder): Entity { - val prefabKey = decoder.decodeSerializableValue(PrefabKey.serializer()) - return (prefabManager[prefabKey] ?: error("Error deserializing, $prefabKey is not a registered prefab")) - } - - override fun serialize(encoder: Encoder, value: Entity) { - encoder.encodeSerializableValue( - PrefabKey.serializer(), - value.get() ?: error("Could not encode prefab entity without a prefab key component") - ) - } -} diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/InheritPrefabsOnLoad.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/InheritPrefabsOnLoad.kt index 0b6378000..2cb13deb2 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/InheritPrefabsOnLoad.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/InheritPrefabsOnLoad.kt @@ -1,9 +1,9 @@ package com.mineinabyss.geary.prefabs.systems -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.prefabs.events.PrefabLoaded import com.mineinabyss.geary.prefabs.helpers.inheritPrefabsIfNeeded -import com.mineinabyss.geary.systems.builders.observe -fun GearyModule.createInheritPrefabsOnLoadListener() = observe() +fun Geary.createInheritPrefabsOnLoadListener() = observe() .exec { entity.inheritPrefabsIfNeeded() } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/TrackPrefabsByKeySystem.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/TrackPrefabsByKeySystem.kt index a58a79416..f88c59a86 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/TrackPrefabsByKeySystem.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/TrackPrefabsByKeySystem.kt @@ -1,15 +1,16 @@ package com.mineinabyss.geary.prefabs.systems import com.mineinabyss.geary.components.relations.NoInherit -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe +import com.mineinabyss.geary.observers.Observer import com.mineinabyss.geary.observers.events.OnSet import com.mineinabyss.geary.prefabs.PrefabKey -import com.mineinabyss.geary.prefabs.prefabs -import com.mineinabyss.geary.systems.builders.observe +import com.mineinabyss.geary.prefabs.Prefabs import com.mineinabyss.geary.systems.query.query -fun GearyModule.createTrackPrefabsByKeyListener() = observe() +fun Geary.createTrackPrefabsByKeyListener(): Observer = observe() .involving(query()).exec { (key) -> - prefabs.manager.registerPrefab(key, entity) + getAddon(Prefabs).manager.registerPrefab(key, entity) entity.addRelation() } diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/ComponentIdSerializerTest.kt similarity index 98% rename from addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt rename to addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/ComponentIdSerializerTest.kt index 77b777dcb..675d906ed 100644 --- a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt +++ b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/ComponentIdSerializerTest.kt @@ -16,7 +16,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class SerializerTest { +class ComponentIdSerializerTest { interface Components @SerialName("test:thing.a") diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/CopyToInstancesTest.kt b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/CopyToInstancesTest.kt index becb93d1a..128c982b4 100644 --- a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/CopyToInstancesTest.kt +++ b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/CopyToInstancesTest.kt @@ -5,30 +5,24 @@ import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances -import com.mineinabyss.geary.serialization.dsl.serialization import com.mineinabyss.geary.serialization.getAllPersisting -import com.mineinabyss.idofront.di.DI +import com.mineinabyss.geary.serialization.serialization +import com.mineinabyss.geary.test.GearyTest import io.kotest.assertions.assertSoftly import io.kotest.matchers.shouldBe import kotlinx.serialization.builtins.serializer -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class CopyToInstancesTest { - @BeforeEach - fun createEngine() { - DI.clear() - geary(TestEngineModule) { - install(Prefabs) - - serialization { - components { - component(String.serializer()) - component(Int.serializer()) - } +class CopyToInstancesTest : GearyTest() { + override fun setupGeary() = geary(TestEngineModule) { + serialization { + components { + component(String.serializer()) + component(Int.serializer()) } } - geary.pipeline.runStartupTasks() + + install(Prefabs) } @Test @@ -38,7 +32,8 @@ class CopyToInstancesTest { set( CopyToInstances( temporary = listOf(42), - persisting = listOf("Hello world") + persisting = listOf("Hello world"), + world = world, ) ) addRelation() diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntitySerializerTest.kt b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntityComponentIdSerializerTest.kt similarity index 68% rename from addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntitySerializerTest.kt rename to addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntityComponentIdSerializerTest.kt index 82cd0247a..e3572dcc9 100644 --- a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntitySerializerTest.kt +++ b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/GearyEntityComponentIdSerializerTest.kt @@ -2,13 +2,13 @@ package com.mineinabyss.geary.prefabs import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.serialization.dsl.serialization +import com.mineinabyss.geary.serialization.SerializableComponents import com.mineinabyss.geary.serialization.formats.YamlFormat -import com.mineinabyss.geary.serialization.serializableComponents +import com.mineinabyss.geary.serialization.serialization import com.mineinabyss.geary.serialization.serializers.GearyEntitySerializer import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer.Companion.provideConfig -import com.mineinabyss.idofront.di.DI +import com.mineinabyss.geary.test.GearyTest import io.kotest.matchers.collections.shouldContainExactly import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -17,18 +17,13 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class GearyEntitySerializerTest { - - init { - DI.clear() - geary(TestEngineModule) { - serialization { - components { - component(A.serializer()) - } +class GearyEntityComponentIdSerializerTest : GearyTest() { + override fun setupGeary() = geary(TestEngineModule) { + serialization { + components { + component(A.serializer()) } } - geary.pipeline.runStartupTasks() } @Serializable @@ -38,7 +33,7 @@ class GearyEntitySerializerTest { @Test fun `GearyEntitySerializer should deserialize to entity correctly`() { // arrange - val format = YamlFormat(serializableComponents.serializers.module) + val format = YamlFormat(getAddon(SerializableComponents).serializers.module) val file = """ thing.a: {} @@ -46,7 +41,7 @@ class GearyEntitySerializerTest { // act val entity = - format.decodeFromString(GearyEntitySerializer, file, overrideSerializersModule = SerializersModule { + format.decodeFromString(GearyEntitySerializer(), file, overrideSerializersModule = SerializersModule { provideConfig(PolymorphicListAsMapSerializer.Config(namespaces = listOf("test"))) }) diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/PrefabTests.kt b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/PrefabTests.kt index 5be3c5c10..1cd216123 100644 --- a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/PrefabTests.kt +++ b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/PrefabTests.kt @@ -3,21 +3,18 @@ package com.mineinabyss.geary.prefabs import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.idofront.di.DI +import com.mineinabyss.geary.serialization.SerializableComponents +import com.mineinabyss.geary.test.GearyTest import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class PrefabTests { +class PrefabTests : GearyTest() { private val testKey = PrefabKey.of("test:1") - @BeforeEach - fun createEngine() { - DI.clear() - geary(TestEngineModule) { - install(Prefabs) - } + override fun setupGeary() = geary(TestEngineModule) { + install(SerializableComponents) + install(Prefabs) } @Test @@ -29,7 +26,7 @@ class PrefabTests { val instance = entity { extend(prefab) } // assert - testKey.toEntity() shouldBe prefab + entityOfOrNull(testKey) shouldBe prefab instance.get().shouldBeNull() } @@ -39,6 +36,6 @@ class PrefabTests { val prefab = entity { set(testKey) } // assert - testKey.toEntityOrNull() shouldBe prefab + entityOfOrNull(testKey) shouldBe prefab } } diff --git a/addons/geary-serialization/build.gradle.kts b/addons/geary-serialization/build.gradle.kts index 3252a1e9c..648c0dc7d 100644 --- a/addons/geary-serialization/build.gradle.kts +++ b/addons/geary-serialization/build.gradle.kts @@ -10,9 +10,6 @@ kotlin { dependencies { implementation(project(":geary-core")) - implementation(libs.uuid) - implementation(idofrontLibs.idofront.di) - api(idofrontLibs.kotlinx.serialization.cbor) api(idofrontLibs.kotlinx.serialization.json) api(libs.okio) diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/EntitySerializationExtensions.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/EntitySerializationExtensions.kt index d655e50a0..ce81a52f3 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/EntitySerializationExtensions.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/EntitySerializationExtensions.kt @@ -4,17 +4,9 @@ import com.mineinabyss.geary.annotations.optin.DangerousComponentOperation import com.mineinabyss.geary.datatypes.Component import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.modules.GearyModule import com.mineinabyss.geary.observers.events.OnAdd -import com.mineinabyss.geary.observers.events.OnSet import com.mineinabyss.geary.serialization.components.Persists -import com.mineinabyss.geary.systems.builders.observe -import com.mineinabyss.geary.systems.query.query -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract import kotlin.reflect.KClass -import kotlin.reflect.typeOf /** @@ -28,7 +20,7 @@ inline fun Entity.setPersisting( noEvent: Boolean = false, ): T { set(component, kClass, noEvent) - setRelation(serializableComponents.persists, componentId(kClass), Persists(), noEvent) + setRelation(world.getAddon(SerializableComponents).persists, world.componentId(kClass), Persists(), noEvent) return component } diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/FileSystem.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/FileSystem.kt new file mode 100644 index 000000000..dea1b412c --- /dev/null +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/FileSystem.kt @@ -0,0 +1,9 @@ +package com.mineinabyss.geary.serialization + +import com.mineinabyss.geary.addons.dsl.createAddon +import okio.FileSystem + +val FileSystem = createAddon( + "File System", + { error("No FileSystem passed into addon, please use FileSystemAddon()") } +) diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/FileSystemAddon.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/FileSystemAddon.kt deleted file mode 100644 index 872853a1c..000000000 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/FileSystemAddon.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.mineinabyss.geary.serialization - -import com.mineinabyss.geary.addons.dsl.GearyAddon -import com.mineinabyss.idofront.di.DI -import okio.FileSystem - -val fileSystem by DI.observe() - -interface FileSystemAddon { - companion object : GearyAddon { - override fun FileSystem.install() { - } - } -} diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/SerializableComponents.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/SerializableComponents.kt index 7bf9ad474..e5670c472 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/SerializableComponents.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/SerializableComponents.kt @@ -1,49 +1,122 @@ package com.mineinabyss.geary.serialization -import com.mineinabyss.geary.addons.GearyPhase -import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault +import com.mineinabyss.geary.addons.dsl.GearyDSL +import com.mineinabyss.geary.addons.dsl.createAddon +import com.mineinabyss.geary.datatypes.Component import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.GearySetup import com.mineinabyss.geary.serialization.components.Persists -import com.mineinabyss.geary.serialization.dsl.SerializableComponentsDSL import com.mineinabyss.geary.serialization.dsl.builders.ComponentSerializersBuilder import com.mineinabyss.geary.serialization.dsl.builders.FormatsBuilder +import com.mineinabyss.geary.serialization.formats.Format import com.mineinabyss.geary.serialization.formats.Formats +import com.mineinabyss.geary.serialization.serializers.ComponentIdSerializer import com.mineinabyss.geary.serialization.serializers.GearyEntitySerializer -import com.mineinabyss.idofront.di.DI +import com.mineinabyss.geary.serialization.serializers.SerializableComponentId +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.modules.* +import kotlinx.serialization.serializerOrNull +import kotlin.reflect.KClass -val serializableComponents by DI.observe() +data class SerializableComponentsBuilder( + val world: Geary, + val serializers: ComponentSerializersBuilder = ComponentSerializersBuilder(), + val formats: FormatsBuilder = FormatsBuilder(), + var overridePersists: ComponentId? = null, + var addGearyEntitySerializer: Boolean = true, +) { + /** Adds a [SerializersModule] for polymorphic serialization of [Component]s within the ECS. */ + inline fun components(crossinline init: PolymorphicModuleBuilder.() -> Unit) { + module { polymorphic(Component::class) { init() } } + } -interface SerializableComponents { - val serializers: ComponentSerializers - val formats: Formats - val persists: ComponentId + fun format(ext: String, format: (SerializersModule) -> Format) { + formats.register(ext, format) + } - interface Builder { - val serializersBuilder: ComponentSerializersBuilder - val formatsBuilder: FormatsBuilder + /** + * Adds a serializable component and registers it with Geary to allow finding the appropriate class via + * component serial name. + */ + inline fun PolymorphicModuleBuilder.component(serializer: KSerializer) { + component(T::class, serializer) } - companion object : GearyAddonWithDefault { - override fun default(): Builder = object : Builder { - override val serializersBuilder = ComponentSerializersBuilder() - override val formatsBuilder = FormatsBuilder() + /** + * Adds a serializable component and registers it with Geary to allow finding the appropriate class via + * component serial name. + */ + @OptIn(InternalSerializationApi::class) + fun PolymorphicModuleBuilder.component( + subclass: KClass, + serializer: KSerializer = subclass.serializerOrNull() + ?: error("No serializer found for $subclass while registering serializable component"), + ) { + val serialName = serializer.descriptor.serialName + if (serializers.serialNameToClass.containsKey(serialName)) { + error("A component with serial name $serialName is already registered") } + serializers.serialNameToClass[serialName] = subclass + subclass(subclass, serializer) + } + + inline fun namedComponent(name: String) { + serializers.serialNameToClass[name] = T::class + } - override fun Builder.install() { - SerializableComponentsDSL(this).apply { - components { - component(GearyEntitySerializer) - } - } - geary.pipeline.runOnOrAfter(GearyPhase.ADDONS_CONFIGURED) { - DI.add(object : SerializableComponents { - override val serializers = serializersBuilder.build() - override val formats = formatsBuilder.build(serializers) - override val persists: ComponentId = componentId() - }) - } + /** Adds a [SerializersModule] to be used for polymorphic serialization within the ECS. */ + inline fun module(init: SerializersModuleBuilder.() -> Unit) { + serializers.modules += SerializersModule { init() } + } + + fun build(): SerializableComponentsModule { + module { + contextual(GearyWorldProvider(world)) + contextual(ComponentIdSerializer(serializers.build(), world)) } + val serializers = serializers.build() + if (addGearyEntitySerializer) { + components { component(GearyEntitySerializer()) } + } + return SerializableComponentsModule( + serializers = serializers, + formats = formats.build(serializers), + persists = overridePersists ?: world.componentId() + ) + } +} + +data class SerializableComponentsModule( + val serializers: ComponentSerializers, + val formats: Formats, + val persists: ComponentId, +) + +val SerializableComponents = createAddon( + "Serializable Components", + { SerializableComponentsBuilder(this) } +) { configuration.build() } + +@GearyDSL +fun GearySetup.serialization(configure: SerializableComponentsBuilder.() -> Unit) = + install(SerializableComponents, configure) + +fun SerializersModule.getWorld(): Geary = (getContextual(Geary::class) as GearyWorldProvider).world + +class GearyWorldProvider(val world: Geary): KSerializer { + override val descriptor: SerialDescriptor = ContextualSerializer(Any::class).descriptor + + override fun deserialize(decoder: Decoder): Geary { + return world + } + + override fun serialize(encoder: Encoder, value: Geary) { } } diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/SerializableComponentsDSL.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/SerializableComponentsDSL.kt deleted file mode 100644 index 807961b6d..000000000 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/SerializableComponentsDSL.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.mineinabyss.geary.serialization.dsl - -import com.mineinabyss.geary.addons.dsl.GearyDSL -import com.mineinabyss.geary.datatypes.Component -import com.mineinabyss.geary.modules.GearyConfiguration -import com.mineinabyss.geary.serialization.SerializableComponents -import com.mineinabyss.geary.serialization.formats.Format -import kotlinx.serialization.InternalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.modules.PolymorphicModuleBuilder -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.SerializersModuleBuilder -import kotlinx.serialization.modules.polymorphic -import kotlinx.serialization.serializerOrNull -import kotlin.reflect.KClass - -@GearyDSL -class SerializableComponentsDSL( - builder: SerializableComponents.Builder -) { - val serializers = builder.serializersBuilder - val formats = builder.formatsBuilder - - /** Adds a [SerializersModule] for polymorphic serialization of [Component]s within the ECS. */ - inline fun components(crossinline init: PolymorphicModuleBuilder.() -> Unit) { - module { polymorphic(Component::class) { init() } } - } - - fun format(ext: String, format: (SerializersModule) -> Format) { - formats.register(ext, format) - } - - /** - * Adds a serializable component and registers it with Geary to allow finding the appropriate class via - * component serial name. - */ - inline fun PolymorphicModuleBuilder.component(serializer: KSerializer) { - component(T::class, serializer) - } - - /** - * Adds a serializable component and registers it with Geary to allow finding the appropriate class via - * component serial name. - */ - @OptIn(InternalSerializationApi::class) - fun PolymorphicModuleBuilder.component( - subclass: KClass, - serializer: KSerializer = subclass.serializerOrNull() - ?: error("No serializer found for $subclass while registering serializable component") - ) { - val serialName = serializer.descriptor.serialName - if (serializers.serialNameToClass.containsKey(serialName)) { - error("A component with serial name $serialName is already registered") - } - serializers.serialNameToClass[serialName] = subclass - subclass(subclass, serializer) - } - - inline fun namedComponent(name: String) { - serializers.serialNameToClass[name] = T::class - } - - /** Adds a [SerializersModule] to be used for polymorphic serialization within the ECS. */ - inline fun module(init: SerializersModuleBuilder.() -> Unit) { - serializers.modules += SerializersModule { init() } - } -} - -@GearyDSL -fun GearyConfiguration.serialization(configure: SerializableComponentsDSL.() -> Unit) = - install(SerializableComponents).also { SerializableComponentsDSL(it).configure() } diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/WithCommonComponentNames.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/WithCommonComponentNames.kt index a52d5888a..be8dcf681 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/WithCommonComponentNames.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/WithCommonComponentNames.kt @@ -3,8 +3,9 @@ package com.mineinabyss.geary.serialization.dsl import com.mineinabyss.geary.components.relations.ChildOf import com.mineinabyss.geary.components.relations.InstanceOf import com.mineinabyss.geary.observers.events.* +import com.mineinabyss.geary.serialization.SerializableComponentsBuilder -fun SerializableComponentsDSL.withCommonComponentNames() { +fun SerializableComponentsBuilder.withCommonComponentNames() { namedComponent("geary:on_add") namedComponent("geary:on_set") namedComponent("geary:on_first_set") diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/builders/FormatsBuilder.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/builders/FormatsBuilder.kt index f9e7e5f94..2d147d21d 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/builders/FormatsBuilder.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/builders/FormatsBuilder.kt @@ -7,9 +7,9 @@ import com.mineinabyss.geary.serialization.formats.SimpleFormats import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.modules.SerializersModule -class FormatsBuilder { - val formats = mutableMapOf Format>() - +data class FormatsBuilder( + val formats: MutableMap Format> = mutableMapOf() +) { /** Registers a [Format] for a file with extension [ext]. */ fun register(ext: String, makeFromat: (SerializersModule) -> Format) { formats[ext] = makeFromat diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/helpers/Component.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/helpers/Component.kt index 6bf50c045..57d22ca0c 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/helpers/Component.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/helpers/Component.kt @@ -2,11 +2,12 @@ package com.mineinabyss.geary.serialization.helpers import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.serialization.serializableComponents +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.serialization.SerializableComponents /** * Gets the id of a component by its serial name. * Throws an error if the component name does not exist. */ -fun componentId(serialName: String): ComponentId = - componentId(serializableComponents.serializers.getClassFor(serialName)) +fun Geary.componentId(serialName: String): ComponentId = + componentId(getAddon(SerializableComponents).serializers.getClassFor(serialName)) diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/GearyEntitySerializer.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/GearyEntitySerializer.kt index 51eff50be..e597acba4 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/GearyEntitySerializer.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/GearyEntitySerializer.kt @@ -2,22 +2,26 @@ package com.mineinabyss.geary.serialization.serializers import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.serialization.getAllPersisting +import com.mineinabyss.geary.serialization.getWorld +import kotlinx.serialization.Contextual import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -typealias SerializableGearyEntity = @Serializable(with = GearyEntitySerializer::class) GearyEntity +//TODO register contextual serializer for world +typealias SerializableGearyEntity = @Contextual GearyEntity - -object GearyEntitySerializer : KSerializer { +class GearyEntitySerializer() : KSerializer { private val componentSerializer = PolymorphicListAsMapSerializer.ofComponents() override val descriptor = SerialDescriptor("geary:entity", componentSerializer.descriptor) override fun deserialize(decoder: Decoder): GearyEntity { - return entity { + val world = decoder.serializersModule.getWorld() + return world.entity { setAll(componentSerializer.deserialize(decoder)) } } diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/PolymorphicListAsMapSerializer.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/PolymorphicListAsMapSerializer.kt index d1e667be4..04b29d159 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/PolymorphicListAsMapSerializer.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/PolymorphicListAsMapSerializer.kt @@ -1,6 +1,7 @@ package com.mineinabyss.geary.serialization.serializers import com.mineinabyss.geary.datatypes.GearyComponent +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.serialization.ComponentSerializers.Companion.fromCamelCaseToSnakeCase import com.mineinabyss.geary.serialization.ComponentSerializers.Companion.hasNamespace @@ -57,7 +58,7 @@ open class PolymorphicListAsMapSerializer( } when (config.onMissingSerializer) { OnMissing.ERROR -> throw it - OnMissing.WARN -> geary.logger.w("No serializer found for $key in namespaces $namespaces, ignoring") + OnMissing.WARN -> Geary.w("No serializer found for $key in namespaces $namespaces, ignoring") OnMissing.IGNORE -> Unit } compositeDecoder.skipMapValue() @@ -70,7 +71,7 @@ open class PolymorphicListAsMapSerializer( config.whenComponentMalformed(key) parentConfig?.whenComponentMalformed?.invoke(key) if (config.skipMalformedComponents) { - geary.logger.w( + Geary.w( "Malformed component $key, ignoring:\n" + it.stackTraceToString() .lineSequence() @@ -96,6 +97,7 @@ open class PolymorphicListAsMapSerializer( namespaces: List, key: String, ): Result> = runCatching { + val namespaces = namespaces.plus("geary").toSet() if (key.startsWith("kotlin.")) { return@runCatching serializersModule.getPolymorphic(polymorphicSerializer.baseClass, key) as KSerializer } diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt index 00de2ef14..2e258ca27 100644 --- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt +++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt @@ -3,9 +3,11 @@ package com.mineinabyss.geary.serialization.serializers import com.mineinabyss.geary.datatypes.Component import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.serialization.serializableComponents +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.serialization.ComponentSerializers +import com.mineinabyss.geary.serialization.SerializableComponents +import kotlinx.serialization.Contextual import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder @@ -13,26 +15,28 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule import kotlin.reflect.KClass -@Serializable(with = SerializableComponentId.Serializer::class) -class SerializableComponentId(val id: ComponentId) { - object Serializer : KSerializer { - override val descriptor = PrimitiveSerialDescriptor("EventComponent", PrimitiveKind.STRING) +typealias SerializableComponentId = @Contextual ComponentId - private val polymorphicListAsMapSerializer = PolymorphicListAsMapSerializer.ofComponents() +class ComponentIdSerializer( + val componentSerializers: ComponentSerializers, + val world: Geary +) : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("EventComponent", PrimitiveKind.STRING) - override fun deserialize(decoder: Decoder): SerializableComponentId { - return SerializableComponentId(componentId(getComponent(decoder.decodeString(), decoder.serializersModule))) - } + private val polymorphicListAsMapSerializer = PolymorphicListAsMapSerializer.ofComponents() - override fun serialize(encoder: Encoder, value: SerializableComponentId) { - TODO() - } + override fun deserialize(decoder: Decoder): SerializableComponentId { + return world.componentId(getComponent(decoder.decodeString(), decoder.serializersModule)) + } + + override fun serialize(encoder: Encoder, value: SerializableComponentId) { + TODO() + } - fun getComponent(name: String, module: SerializersModule): KClass { - val namespaces = polymorphicListAsMapSerializer - .getParentConfig(module)?.namespaces - ?: emptyList() - return serializableComponents.serializers.getClassFor(name, namespaces) - } + fun getComponent(name: String, module: SerializersModule): KClass { + val namespaces = polymorphicListAsMapSerializer + .getParentConfig(module)?.namespaces + ?: emptyList() + return componentSerializers.getClassFor(name, namespaces) } } diff --git a/addons/geary-serialization/src/jvmMain/kotlin/com/mineinabyss/geary/serialization/formats/YamlFormat.kt b/addons/geary-serialization/src/jvmMain/kotlin/com/mineinabyss/geary/serialization/formats/YamlFormat.kt index a86c2f896..984b6f061 100644 --- a/addons/geary-serialization/src/jvmMain/kotlin/com/mineinabyss/geary/serialization/formats/YamlFormat.kt +++ b/addons/geary-serialization/src/jvmMain/kotlin/com/mineinabyss/geary/serialization/formats/YamlFormat.kt @@ -11,24 +11,31 @@ import org.intellij.lang.annotations.Language import java.io.InputStream class YamlFormat( - module: SerializersModule + module: SerializersModule, + configuration: Configuration = Configuration(), ) : Format { + class Configuration( + val regular: YamlConfiguration = YamlConfiguration( + encodeDefaults = false, + polymorphismStyle = PolymorphismStyle.Property, + polymorphismPropertyName = "type", + ), + val nonStrict: YamlConfiguration = YamlConfiguration( + encodeDefaults = false, + strictMode = false, + polymorphismStyle = PolymorphismStyle.Property, + polymorphismPropertyName = "type", + ), + ) override val ext = "yml" private val nonStrictYaml = Yaml( - configuration = YamlConfiguration( - encodeDefaults = false, - strictMode = false, - polymorphismStyle = PolymorphismStyle.Property - ) + configuration = configuration.nonStrict ) val regularYaml = Yaml( serializersModule = module, - configuration = YamlConfiguration( - encodeDefaults = false, - polymorphismStyle = PolymorphismStyle.Property - ) + configuration = configuration.regular ) override fun decodeFromFile( diff --git a/addons/geary-uuid/build.gradle.kts b/addons/geary-uuid/build.gradle.kts index 3ef506709..a342a5c64 100644 --- a/addons/geary-uuid/build.gradle.kts +++ b/addons/geary-uuid/build.gradle.kts @@ -9,9 +9,7 @@ kotlin { dependencies { implementation(project(":geary-core")) - api(libs.uuid) implementation(libs.atomicfu) - implementation(idofrontLibs.idofront.di) } } } diff --git a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUID2GearyMap.kt b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUID2GearyMap.kt index 592cb5caf..51b6b42e6 100644 --- a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUID2GearyMap.kt +++ b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUID2GearyMap.kt @@ -1,33 +1,32 @@ package com.mineinabyss.geary.uuid -import com.benasher44.uuid.Uuid -import com.mineinabyss.geary.datatypes.GearyEntity -import com.mineinabyss.geary.helpers.toGeary +import com.mineinabyss.geary.datatypes.EntityId import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.atomicfu.locks.synchronized +import kotlin.uuid.Uuid interface UUID2GearyMap { - operator fun get(uuid: Uuid): GearyEntity? + operator fun get(uuid: Uuid): EntityId? - operator fun set(uuid: Uuid, entity: GearyEntity): GearyEntity? + operator fun set(uuid: Uuid, entity: EntityId): EntityId? operator fun contains(uuid: Uuid): Boolean - fun remove(uuid: Uuid): GearyEntity? + fun remove(uuid: Uuid): EntityId? } class SimpleUUID2GearyMap : UUID2GearyMap { private val map = mutableMapOf() - override operator fun get(uuid: Uuid): GearyEntity? = - map[uuid]?.toGeary() + override operator fun get(uuid: Uuid): EntityId? = + map[uuid]?.toULong() - override operator fun set(uuid: Uuid, entity: GearyEntity): GearyEntity? = - map.put(uuid, entity.id.toLong())?.toGeary() + override operator fun set(uuid: Uuid, entity: EntityId): EntityId? = + map.put(uuid, entity.toLong())?.toULong() override operator fun contains(uuid: Uuid): Boolean = map.containsKey(uuid) - override fun remove(uuid: Uuid): GearyEntity? = - map.remove(uuid)?.toGeary() + override fun remove(uuid: Uuid): EntityId? = + map.remove(uuid)?.toULong() } @@ -35,8 +34,8 @@ class SynchronizedUUID2GearyMap : UUID2GearyMap { private val unsafe = SimpleUUID2GearyMap() private val lock = SynchronizedObject() - override fun get(uuid: Uuid): GearyEntity? = synchronized(lock) { unsafe[uuid] } - override fun set(uuid: Uuid, entity: GearyEntity): GearyEntity? = synchronized(lock) { unsafe.set(uuid, entity) } + override fun get(uuid: Uuid): EntityId? = synchronized(lock) { unsafe[uuid] } + override fun set(uuid: Uuid, entity: EntityId): EntityId? = synchronized(lock) { unsafe.set(uuid, entity) } override fun contains(uuid: Uuid): Boolean = synchronized(lock) { unsafe.contains(uuid) } - override fun remove(uuid: Uuid): GearyEntity? = synchronized(lock) { unsafe.remove(uuid) } + override fun remove(uuid: Uuid): EntityId? = synchronized(lock) { unsafe.remove(uuid) } } diff --git a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUIDTracking.kt b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUIDTracking.kt index ebf5d2c9b..69651f947 100644 --- a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUIDTracking.kt +++ b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUIDTracking.kt @@ -1,20 +1,13 @@ package com.mineinabyss.geary.uuid -import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.uuid.systems.untrackUuidOnRemove +import com.mineinabyss.geary.addons.dsl.createAddon import com.mineinabyss.geary.uuid.systems.trackUUIDOnAdd -import com.mineinabyss.idofront.di.DI - -val uuid2Geary by DI.observe() - -object UUIDTracking : GearyAddonWithDefault { - override fun default() = SimpleUUID2GearyMap() +import com.mineinabyss.geary.uuid.systems.untrackUuidOnRemove - override fun UUID2GearyMap.install() { - geary.run { - trackUUIDOnAdd() - untrackUuidOnRemove() - } +val UUIDTracking = createAddon("UUID Tracking", { SimpleUUID2GearyMap() }) { + onStart { + trackUUIDOnAdd(configuration) + untrackUuidOnRemove(configuration) } } + diff --git a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/TrackUuidOnAdd.kt b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/TrackUuidOnAdd.kt index ac72c70a0..89d293c56 100644 --- a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/TrackUuidOnAdd.kt +++ b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/TrackUuidOnAdd.kt @@ -1,22 +1,21 @@ package com.mineinabyss.geary.uuid.systems -import com.benasher44.uuid.Uuid -import com.benasher44.uuid.uuid4 -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.events.OnSet -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query +import com.mineinabyss.geary.uuid.UUID2GearyMap import com.mineinabyss.geary.uuid.components.RegenerateUUIDOnClash -import com.mineinabyss.geary.uuid.uuid2Geary +import kotlin.uuid.Uuid -fun GearyModule.trackUUIDOnAdd() = observe().involving(query()).exec { (uuid) -> +fun Geary.trackUUIDOnAdd(uuid2Geary: UUID2GearyMap) = observe().involving(query()).exec { (uuid) -> val regenerateUUIDOnClash = entity.get() if (uuid in uuid2Geary) if (regenerateUUIDOnClash != null) { - val newUuid = uuid4() + val newUuid = Uuid.random() entity.set(newUuid) - uuid2Geary[newUuid] = entity + uuid2Geary[newUuid] = entity.id } else error("Tried tracking entity $entity with already existing uuid $uuid") else - uuid2Geary[uuid] = entity + uuid2Geary[uuid] = entity.id } diff --git a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/UnTrackUuidOnRemove.kt b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/UnTrackUuidOnRemove.kt index d9a51f939..927e1d0ec 100644 --- a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/UnTrackUuidOnRemove.kt +++ b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/UnTrackUuidOnRemove.kt @@ -1,12 +1,11 @@ package com.mineinabyss.geary.uuid.systems -import com.benasher44.uuid.Uuid -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.events.OnRemove -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query -import com.mineinabyss.geary.uuid.uuid2Geary +import com.mineinabyss.geary.uuid.UUID2GearyMap +import kotlin.uuid.Uuid -fun GearyModule.untrackUuidOnRemove() = observe() +fun Geary.untrackUuidOnRemove(uuid2Geary: UUID2GearyMap) = observe() .involving(query()).exec { (uuid) -> uuid2Geary.remove(uuid) } - diff --git a/build.gradle.kts b/build.gradle.kts index c1ddb768e..aeabfb97a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,6 +41,7 @@ allprojects { optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") optIn("kotlin.time.ExperimentalTime") optIn("kotlin.ExperimentalUnsignedTypes") + optIn("kotlin.uuid.ExperimentalUuidApi") optIn("kotlinx.serialization.ExperimentalSerializationApi") optIn("kotlin.RequiresOptIn") } diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/events/EventCalls.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/events/EventCalls.kt index 097b29ac0..21889f866 100644 --- a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/events/EventCalls.kt +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/events/EventCalls.kt @@ -3,6 +3,7 @@ package com.mineinabyss.geary.benchmarks.events import com.mineinabyss.geary.benchmarks.helpers.oneMil import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.systems.builders.observe @@ -18,7 +19,7 @@ class EventCalls { @Setup(Level.Invocation) fun setupPerInvocation() { geary(TestEngineModule) - targets = (1..oneMil).map { entity { set(it) } } + targets = (1..oneMil).map { entity().apply { set(it) } } createListener() } @@ -39,6 +40,7 @@ class EventCalls { targets[it].emit() } } + } fun main() { diff --git a/geary-core/build.gradle.kts b/geary-core/build.gradle.kts index d541a01e6..2cbf1f3a0 100644 --- a/geary-core/build.gradle.kts +++ b/geary-core/build.gradle.kts @@ -12,19 +12,21 @@ kotlin { implementation(libs.androidx.collection) implementation(idofrontLibs.kotlin.reflect) - api(idofrontLibs.idofront.di) + api(libs.koin.core) api(idofrontLibs.kermit) api(idofrontLibs.kotlinx.coroutines) } } - val commonTest by getting { + + val jvmTest by getting { dependencies { + implementation(project(":geary-test")) implementation(kotlin("test")) implementation(idofrontLibs.kotlinx.coroutines.test) implementation(idofrontLibs.kotest.assertions) implementation(idofrontLibs.kotest.property) - implementation(idofrontLibs.idofront.di) + implementation(libs.koin.test) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/addons/Namespaced.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/addons/Namespaced.kt index 4a2244413..7f006556b 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/addons/Namespaced.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/addons/Namespaced.kt @@ -1,8 +1,8 @@ package com.mineinabyss.geary.addons import com.mineinabyss.geary.addons.dsl.GearyDSL -import com.mineinabyss.geary.modules.GearyConfiguration +import com.mineinabyss.geary.modules.GearySetup @GearyDSL -class Namespaced(val namespace: String, val gearyConf: GearyConfiguration) +class Namespaced(val namespace: String, val setup: GearySetup) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/addons/dsl/GearyAddon.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/addons/dsl/GearyAddon.kt index ccdb6ecb8..ff69b2ed3 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/addons/dsl/GearyAddon.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/addons/dsl/GearyAddon.kt @@ -1,9 +1,115 @@ package com.mineinabyss.geary.addons.dsl -interface GearyAddonWithDefault: GearyAddon { +import co.touchlab.kermit.Logger +import com.mineinabyss.geary.addons.GearyPhase +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.GearySetup +import org.koin.core.Koin +import org.koin.core.KoinApplication +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import org.koin.core.module.Module +import kotlin.jvm.JvmName + +interface GearyAddonWithDefault : GearyAddon { fun default(): Module } interface GearyAddon { fun Module.install() } + +data class Addon( + val name: String, + val defaultConfiguration: Geary.() -> Configuration, + val onInstall: Geary.(Configuration) -> Instance, +) { + fun withConfig(customConfiguration: Geary.() -> Configuration): Addon { + return copy(defaultConfiguration = customConfiguration) + } +} + +data class AddonSetup( + val name: String, + val configuration: Configuration, + val application: KoinApplication, +): KoinComponent { + override fun getKoin(): Koin = application.koin + val logger = get().withTag(name) + val geary: Geary = Geary(application, logger) + + /** Runs a block during [GearyPhase.INIT_COMPONENTS] */ + fun components(configure: Geary.() -> Unit) { + on(GearyPhase.INIT_COMPONENTS) { + configure(geary) + } + } + + /** Runs a block during [GearyPhase.INIT_SYSTEMS] */ + fun systems(configure: Geary.() -> Unit) { + on(GearyPhase.INIT_SYSTEMS) { + configure(geary) + } + } + + /** Runs a block during [GearyPhase.INIT_ENTITIES] */ + fun entities(configure: Geary.() -> Unit) { + on(GearyPhase.INIT_ENTITIES) { + configure(geary) + } + } + + fun onStart(run: Geary.() -> Unit) { + on(GearyPhase.ENABLE) { + run(geary) + } + } + + /** + * Allows defining actions that should run at a specific phase during startup + * + * Within its context, invoke a [GearyPhase] to run something during it, ex: + * + * ``` + * GearyLoadPhase.ENABLE { + * // run code here + * } + * ``` + */ + fun on(phase: GearyPhase, run: () -> Unit) { + geary.pipeline.runOnOrAfter(phase, run) + } + + fun inject(vararg modules: Module) { + application.modules(*modules) + } +} + +@JvmName("createAddon0") +fun createAddon( + name: String, + init: AddonSetup.() -> Unit, +): Addon = Addon(name, { }) { + init(AddonSetup(name, it, application)) +} + +@JvmName("createAddon1") +fun createAddon( + name: String, + configuration: Geary.() -> Conf, + init: AddonSetup.() -> Unit = {}, +): Addon = Addon(name, configuration) { conf -> + init(AddonSetup(name, conf, application)) + conf +} + +@JvmName("createAddon2") +fun createAddon( + name: String, + configuration: Geary.() -> Conf, + init: AddonSetup.() -> Inst, +): Addon { + return Addon(name, configuration) { + init(AddonSetup(name, it, application)) + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/ReservedComponents.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/ReservedComponents.kt new file mode 100644 index 000000000..9f2f56d94 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/ReservedComponents.kt @@ -0,0 +1,22 @@ +package com.mineinabyss.geary.components + +import com.mineinabyss.geary.components.relations.ChildOf +import com.mineinabyss.geary.datatypes.entityTypeOf + +/** + * Entity id references that are used internally [EntityProvider] should ensure to sequentially create entities to + * account for these. As a result, these ids should be sequential, starting at zero. + * + * Keeping these as const vals helps improve performance in tight loops that might be referencing these often. + */ +object ReservedComponents { + const val COMPONENT_INFO = 0uL + const val ANY = 1uL + const val CHILD_OF = 2uL + + val reservedComponents = mapOf( + ComponentInfo::class to COMPONENT_INFO, + Any::class to ANY, + ChildOf::class to CHILD_OF, + ) +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt index 0b519e8e1..8fa017d71 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt @@ -5,11 +5,13 @@ import com.mineinabyss.geary.components.EntityName import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.engine.Engine import com.mineinabyss.geary.helpers.* -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.relationOf import com.mineinabyss.geary.observers.events.OnAdd import com.mineinabyss.geary.systems.accessors.AccessorOperations import com.mineinabyss.geary.systems.accessors.RelationWithData -import kotlin.jvm.JvmInline +import org.koin.core.Koin +import org.koin.core.KoinApplication import kotlin.reflect.KClass typealias GearyEntity = Entity @@ -19,41 +21,36 @@ typealias GearyEntity = Entity * * Provides some useful functions, so we aren't forced to go through [Engine] every time we want to do some things. */ -@JvmInline -value class Entity(val id: EntityId) { - inline val idL get() = id.toLong() - - private val entityProvider get() = geary.entityProvider - private val queryManager get() = geary.queryManager - private val read get() = geary.read - private val write get() = geary.write +//TODO add require checks for entities across different worlds +class Entity(val id: EntityId, val world: Geary) { + val comp get() = world.components /** * Gets this entity's type (the ids of components added to it) * or throws an error if it is no longer active on the koinGet(). */ - val type: EntityType get() = entityProvider.getType(this) + val type: EntityType get() = world.records.getType(id) - val children: List - get() = queryManager.getEntitiesMatching(family { - hasRelation(geary.components.childOf, this@Entity.id) - }) + val children: EntityArray + get() = world.queryManager.getEntitiesMatching(family { + hasRelation(comp.childOf, this@Entity.id) + }).toEntityArray(world) - val instances: List - get() = queryManager.getEntitiesMatching(family { - hasRelation(geary.components.instanceOf, this@Entity.id) - }) + val instances: EntityArray + get() = world.queryManager.getEntitiesMatching(family { + hasRelation(comp.instanceOf, this@Entity.id) + }).toEntityArray(world) - val prefabs: List - get() = getRelations(geary.components.instanceOf, geary.components.any).map { it.target.toGeary() } + val prefabs: EntityArray + get() = getRelations(comp.instanceOf, comp.any).map { it.target }.toULongArray().toEntityArray(world) /** Remove this entity from the ECS. */ fun removeEntity() { - entityProvider.remove(this) + world.entityRemoveProvider.remove(id) } /** Checks whether this entity has not been removed. */ - fun exists(): Boolean = read.exists(this) + fun exists(): Boolean = world.read.exists(id) /** * Sets a component that holds data for this entity @@ -63,14 +60,14 @@ value class Entity(val id: EntityId) { inline fun set( component: T, kClass: KClass = T::class, - noEvent: Boolean = false - ): Unit = set(component, componentId(kClass), noEvent) + noEvent: Boolean = false, + ): Unit = set(component, world.componentId(kClass), noEvent) fun set( component: Component, componentId: ComponentId, - noEvent: Boolean = false - ): Unit = write.setComponentFor(this, componentId, component, noEvent) + noEvent: Boolean = false, + ): Unit = world.write.setComponentFor(id, componentId, component, noEvent) /** Sets components that hold data for this entity */ fun setAll(components: Collection, override: Boolean = true) { @@ -85,7 +82,7 @@ value class Entity(val id: EntityId) { * @param noEvent If true, will not fire an [OnAdd] event. */ fun add(component: ComponentId, noEvent: Boolean = false) { - write.addComponentFor(this, component, noEvent) + world.write.addComponentFor(id, component, noEvent) } /** @@ -94,7 +91,7 @@ value class Entity(val id: EntityId) { * @param noEvent If true, will not fire an [OnAdd] event. */ inline fun add(noEvent: Boolean = false) { - add(componentId(), noEvent) + add(world.componentId(), noEvent) } /** @@ -112,15 +109,15 @@ value class Entity(val id: EntityId) { * @return Whether the component was present before removal. */ inline fun remove(noEvent: Boolean = false): Boolean = - remove(componentId(), noEvent) + remove(world.componentId(), noEvent) /** Removes a component whose class is [kClass] from this entity. */ fun remove(kClass: KClass<*>, noEvent: Boolean = false): Boolean = - remove(componentId(kClass), noEvent) + remove(world.componentId(kClass), noEvent) /** Removes a component with id [component] from this entity. */ fun remove(component: ComponentId, noEvent: Boolean = false): Boolean = - write.removeComponentFor(this, component, noEvent) + world.write.removeComponentFor(id, component, noEvent) /** * Removes a list of [components] from this entity. @@ -132,7 +129,7 @@ value class Entity(val id: EntityId) { /** Clears all components on this entity. */ fun clear() { - write.clearEntity(this) + world.write.clearEntity(id) } /** Gets a component of type [T] on this entity. */ @@ -140,37 +137,37 @@ value class Entity(val id: EntityId) { /** @see get */ inline fun get(kClass: KClass): T? = - get(componentId(kClass)) as? T + get(world.componentId(kClass)) as? T /** Gets a [component] which holds data from this entity. Use [has] if the component is not to hold data. */ fun get(component: ComponentId): Component? = - read.getComponentFor(this, component) + world.read.get(id, component) /** Gets a component of type [T] or sets a [default] if no component was present. */ inline fun getOrSet( kClass: KClass = T::class, - default: () -> T + default: () -> T, ): T = get(kClass) ?: default().also { set(it) } /** Gets all the components on this entity, as well as relations in the form of [RelationComponent]. */ - fun getAll(): Set = read.getComponentsFor(this).toSet() + fun getAll(): Set = world.read.getAll(id).toSet() /** * Checks whether this entity is an instance of another [entity] * (the other is the prefab this entity was made from). */ - fun instanceOf(entity: Entity): Boolean = has(Relation.of(geary.components.instanceOf, entity.id).id) + fun instanceOf(entity: Entity): Boolean = has(Relation.of(comp.instanceOf, entity.id).id) /** Checks whether this entity has a component of type [T], regardless of it holding data. */ inline fun has(): Boolean = has(T::class) /** @see has */ inline fun has(kClass: KClass): Boolean = - has(componentId(kClass)) + has(world.componentId(kClass)) /** Checks whether this entity has a [component], regardless of it holding data. */ fun has(component: ComponentId): Boolean = - read.hasComponentFor(this, component) + world.read.has(id, component) /** * Checks whether an entity has all of [components] set or added. @@ -181,90 +178,92 @@ value class Entity(val id: EntityId) { /** Adds a [base] entity to this entity. */ fun extend(base: Entity) { - write.extendFor(this, base) + requireSameWorldAs(base) + world.write.extendFor(id, base.id) } - - /** Adds a [prefab] entity to this entity. */ - fun Entity.removePrefab(prefab: Entity) { - remove(Relation.of(geary.components.instanceOf, prefab.id).id) + + /** Removes a [prefab] from this entity. */ + fun removePrefab(prefab: Entity) { + requireSameWorldAs(prefab) + remove(Relation.of(comp.instanceOf, prefab.id).id) } // Relations /** Gets the data stored under the relation of kind [K] and target [T]. */ inline fun getRelation(): K? { - return getRelation(component()) + return getRelation(world.component()) } /** Gets the data stored under the relation of kind [K] and target [target]. */ inline fun getRelation(target: Entity): K? { - return get(Relation.of(target).id) as? K + return get(world.relationOf(target).id) as? K } /** Like [getRelations], but reads appropriate data as requested and puts it in a [RelationWithData] object. */ @Suppress("UNCHECKED_CAST") // Intrnal logic ensures cast always succeeds inline fun getRelationsWithData(): List> = - geary.read.getRelationsWithDataFor( - this, - componentIdWithNullable(), - componentIdWithNullable() + world.read.getRelationsWithDataFor( + id, + world.componentIdWithNullable(), + world.componentIdWithNullable() ) as List> fun getRelationsByKind(kind: ComponentId): List = - getRelations(kind, geary.components.any) + getRelations(kind, comp.any) /** Queries for relations using the same format as [AccessorOperations.getRelations]. */ inline fun getRelations(): List = - getRelations(componentIdWithNullable(), componentIdWithNullable()) + getRelations(world.componentIdWithNullable(), world.componentIdWithNullable()) fun getRelations(kind: ComponentId, target: EntityId): List = - read.getRelationsFor(this, kind, target) + world.read.getRelationsFor(id, kind, target) inline fun hasRelation(): Boolean = - hasRelation(component()) + hasRelation(world.component()) inline fun hasRelation(target: Entity): Boolean = - has(Relation.of(target).id) + has(world.relationOf(target).id) inline fun setRelation(data: K, noEvent: Boolean = false) { - setRelation(data, component(), noEvent) + setRelation(data, world.component(), noEvent) } inline fun setRelation(data: K, target: Entity, noEvent: Boolean = false) { - setRelation(componentId(), target.id, data, noEvent) + setRelation(world.componentId(), target.id, data, noEvent) } fun setRelation(kind: ComponentId, target: EntityId, data: Component, noEvent: Boolean = false) { - geary.write.setComponentFor(this, Relation.of(kind, target).id, data, noEvent) + world.write.setComponentFor(id, Relation.of(kind, target).id, data, noEvent) } inline fun addRelation(noEvent: Boolean = false) { - addRelation(component(), noEvent) + addRelation(world.component(), noEvent) } inline fun addRelation(target: Entity, noEvent: Boolean = false) { - geary.write.addComponentFor(this, Relation.of(target).id, noEvent) + world.write.addComponentFor(id, world.relationOf(target).id, noEvent) } fun addRelation(kind: ComponentId, target: EntityId, noEvent: Boolean = false) { - geary.write.addComponentFor(this, Relation.of(kind, target).id, noEvent) + world.write.addComponentFor(id, Relation.of(kind, target).id, noEvent) } inline fun removeRelation(noEvent: Boolean = false): Boolean { - return removeRelation(component(), noEvent) + return removeRelation(world.component(), noEvent) } inline fun removeRelation(target: Entity, noEvent: Boolean = false): Boolean { - return geary.write.removeComponentFor(this, Relation.of(target).id, noEvent) + return world.write.removeComponentFor(id, world.relationOf(target).id, noEvent) } // Events - inline fun emit(data: T? = null, involving: ComponentId = NO_COMPONENT) { - emit(componentId(), data, involving) + inline fun emit(data: T? = null, involving: ComponentId = NO_COMPONENT) { + emit(world.componentId(), data, involving) } fun emit(event: ComponentId, data: Any? = null, involving: ComponentId = NO_COMPONENT) { - geary.eventRunner.callEvent(event, data, involving, this) + world.eventRunner.callEvent(event, data, involving, id) } // Prefabs @@ -287,7 +286,7 @@ value class Entity(val id: EntityId) { return collectPrefabs(collected, new) } - private tailrec fun deepInstanceOf(seen: MutableSet, search: List, prefab: Entity): Boolean { + private tailrec fun deepInstanceOf(seen: MutableSet, search: EntityArray, prefab: Entity): Boolean { if (search.isEmpty()) return false if (search.any { it.instanceOf(prefab) }) return true seen.addAll(search) @@ -306,7 +305,7 @@ value class Entity(val id: EntityId) { return child?.lookup(remaining) } - operator fun component1(): EntityId = id +// operator fun component1(): EntityId = id // Dangerous operations @@ -325,4 +324,23 @@ value class Entity(val id: EntityId) { fun getOrSet(default: () -> Unit) { getOrSet { } } + + private fun requireSameWorldAs(other: Entity) = require(world.application == other.world.application) { + "Entities must be in the same world to interact with each other. " + + "This entity is in ${world.stringify()}, while the other is in ${other.world.stringify()}" + } + + override fun toString(): String = "Entity($id, world=${world.stringify()})" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Entity) return false + return id == other.id && world.application == other.world.application + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + world.application.hashCode() + return result + } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityIdArray.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityIdArray.kt new file mode 100644 index 000000000..f73c95f83 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityIdArray.kt @@ -0,0 +1,44 @@ +package com.mineinabyss.geary.datatypes + +import com.mineinabyss.geary.modules.Geary + +typealias EntityIdArray = ULongArray + +fun EntityIdArray.toEntityArray(world: Geary): EntityArray { + return EntityArray(world, this) +} + +class EntityArray( + val world: Geary, + val ids: EntityIdArray, +) : Collection { + override val size: Int get() = ids.size + + override fun isEmpty(): Boolean = ids.isEmpty() + + override fun iterator(): Iterator = object : Iterator { + private var index = 0 + override fun hasNext(): Boolean = index < ids.size + override fun next(): Entity = GearyEntity(ids[index++], world) + } + + override fun containsAll(elements: Collection): Boolean { + return elements.all { contains(it) } + } + + override fun contains(element: Entity): Boolean { + return ids.contains(element.id) + } + + inline fun fastForEach(action: (Entity) -> Unit) { + for (i in ids.indices) action(GearyEntity(ids[i], world)) + } + + inline fun flatMap(transform: (Entity) -> EntityArray): EntityArray { + return ids.flatMapTo(arrayListOf()) { transform(Entity(it, world)).ids }.toULongArray().toEntityArray(world) + } + + operator fun minus(other: Collection): EntityArray { + return ids.minus(other.map { it.id }.toSet()).toULongArray().toEntityArray(world) + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityStack.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityStack.kt index d2466551d..ddb52d8b9 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityStack.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityStack.kt @@ -1,16 +1,14 @@ package com.mineinabyss.geary.datatypes -import com.mineinabyss.geary.helpers.toGeary - class EntityStack( @PublishedApi - internal val stack: ArrayDeque = ArrayDeque() + internal val stack: ArrayDeque = ArrayDeque(), ) { - fun push(entity: Entity) { - stack.add(entity.id.toLong()) + fun push(entity: EntityId) { + stack.add(entity.toLong()) } - inline fun popOrElse(orElse: () -> Entity): Entity = + inline fun popOrElse(orElse: () -> EntityId): EntityId = if (stack.isEmpty()) orElse() - else stack.removeFirst().toGeary() + else stack.removeFirst().toULong() } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityType.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityType.kt index c03cb4a61..f81a4d33b 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityType.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityType.kt @@ -87,7 +87,7 @@ class EntityType private constructor( } override fun toString(): String = - inner.joinToString(", ", prefix = "[", postfix = "]") { it.readableString() } + inner.joinToString(", ", prefix = "[", postfix = "]") { it.readableString(TODO()) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Relation.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Relation.kt index 1992dc1a1..31585189d 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Relation.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Relation.kt @@ -3,6 +3,7 @@ package com.mineinabyss.geary.datatypes import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.componentIdWithNullable import com.mineinabyss.geary.helpers.readableString +import com.mineinabyss.geary.modules.Geary import kotlin.jvm.JvmInline import kotlin.reflect.KClass @@ -33,7 +34,7 @@ value class Relation private constructor( override fun compareTo(other: Relation): Int = id.compareTo(other.id) - override fun toString(): String = "${kind.readableString()} to ${target.readableString()}" + override fun toString(): String = "${kind.readableString(TODO())} to ${target.readableString(TODO())}" companion object { fun of( @@ -45,15 +46,6 @@ value class Relation private constructor( or (target and RELATION_TARGET_MASK) // Add target, stripping any type roles ) - fun of(kind: KClass<*>, target: KClass<*>): Relation = - of(componentId(kind), componentId(target)) - - inline fun of(): Relation = - of(componentIdWithNullable(), componentId()) - - inline fun of(target: Entity): Relation = - of(componentIdWithNullable(), target.id) - /** * Creates a relation from an id that is assumed to be valid. Use this to avoid boxing Relation because of * the nullable type on [toRelation]. diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt index ee07ec83e..2ce4bb593 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt @@ -1,36 +1,39 @@ package com.mineinabyss.geary.datatypes.family +import com.mineinabyss.geary.components.ReservedComponents import com.mineinabyss.geary.datatypes.* import com.mineinabyss.geary.engine.archetypes.Archetype -import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.helpers.componentIdWithNullable -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.engine.id +import com.mineinabyss.geary.engine.idWithNullable +import com.mineinabyss.geary.modules.Geary inline fun family(init: MutableFamily.Selector.And.() -> Unit): Family.Selector.And { return MutableFamily.Selector.And().apply(init) } -sealed class MutableFamily : Family { - sealed class Leaf : MutableFamily() { - class Component( - override var component: ComponentId - ) : Leaf(), Family.Leaf.Component +sealed interface MutableFamily : Family { +// val comp: ComponentProvider - class AnyToTarget( + sealed interface Leaf : MutableFamily { + data class Component( + override var component: ComponentId, + ) : Leaf, Family.Leaf.Component + + data class AnyToTarget( override var target: EntityId, - override val kindMustHoldData: Boolean - ) : Leaf(), Family.Leaf.AnyToTarget + override val kindMustHoldData: Boolean, + ) : Leaf, Family.Leaf.AnyToTarget - class KindToAny( + data class KindToAny( override var kind: ComponentId, - override val targetMustHoldData: Boolean - ) : Leaf(), Family.Leaf.KindToAny + override val targetMustHoldData: Boolean, + ) : Leaf, Family.Leaf.KindToAny } - sealed class Selector : MutableFamily(), Family.Selector { + sealed class Selector : MutableFamily, Family.Selector { class And( - and: MutableList = mutableListOf() + and: MutableList = mutableListOf(), ) : Selector(), Family.Selector.And { init { elements.addAll(and) @@ -40,7 +43,7 @@ sealed class MutableFamily : Family { } class AndNot( - andNot: MutableList = mutableListOf() + andNot: MutableList = mutableListOf(), ) : Selector(), Family.Selector.AndNot { init { elements.addAll(andNot) @@ -50,7 +53,7 @@ sealed class MutableFamily : Family { } class Or( - or: MutableList = mutableListOf() + or: MutableList = mutableListOf(), ) : Selector(), Family.Selector.Or { init { elements.addAll(or) @@ -93,8 +96,9 @@ sealed class MutableFamily : Family { kind: ComponentId, target: EntityId, ) { - val specificKind = kind and ENTITY_MASK != geary.components.any - val specificTarget = target and ENTITY_MASK != geary.components.any + val any = ReservedComponents.ANY + val specificKind = kind and ENTITY_MASK != any + val specificTarget = target and ENTITY_MASK != any return when { specificKind && specificTarget -> has(Relation.of(kind, target).id) specificTarget -> add(Leaf.AnyToTarget(target, kind.holdsData())) @@ -103,14 +107,14 @@ sealed class MutableFamily : Family { } } - inline fun hasRelation(): Unit = hasRelation(componentIdWithNullable()) + inline fun Geary.hasRelation(): Unit = hasRelation(componentProvider.idWithNullable()) - inline fun hasRelation(target: EntityId) { - val kind = componentIdWithNullable() + inline fun Geary.hasRelation(target: EntityId) { + val kind = componentProvider.idWithNullable() hasRelation(kind, target) } - inline fun hasRelation(target: Entity): Unit = hasRelation(target.id) + inline fun Geary.hasRelation(target: Entity): Unit = hasRelation(target.id) inline fun or(init: Or.() -> Unit) { @@ -125,11 +129,11 @@ sealed class MutableFamily : Family { add(AndNot().apply(init)) } - inline fun has(): Unit = - has(componentId()) + inline fun Geary.has(): Unit = + has(componentProvider.id()) - inline fun hasSet(): Unit = - hasSet(componentId()) + inline fun Geary.hasSet(): Unit = + hasSet(componentProvider.id()) fun has(vararg componentIds: ComponentId) { has(componentIds) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt index 11a6dfc16..b6109bd40 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt @@ -2,10 +2,10 @@ package com.mineinabyss.geary.datatypes.maps import androidx.collection.mutableObjectListOf import com.mineinabyss.geary.datatypes.BucketedULongArray -import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId +import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.engine.archetypes.Archetype - open class ArrayTypeMap : TypeMap { @PublishedApi internal val archList = mutableObjectListOf() @@ -16,12 +16,12 @@ open class ArrayTypeMap : TypeMap { // We don't return nullable record to avoid boxing. // Accessing an entity that doesn't exist is indicative of a problem elsewhere and should be made obvious. - open fun getArchAndRow(entity: Entity): ULong { - return archAndRow[entity.id.toInt()] + open fun getArchAndRow(entity: EntityId): ULong { + return archAndRow[entity.toInt()] } - override fun set(entity: Entity, archetype: Archetype, row: Int) { - val id = entity.id.toInt() + override fun set(entity: EntityId, archetype: Archetype, row: Int) { + val id = entity.toInt() archAndRow[id] = (indexOrAdd(archetype).toULong() shl 32) or row.toULong() } @@ -35,19 +35,23 @@ open class ArrayTypeMap : TypeMap { } else index } - override fun remove(entity: Entity) { - val id = entity.id.toInt() + override fun remove(entity: EntityId) { + val id = entity.toInt() archAndRow[id] = 0UL } - override operator fun contains(entity: Entity): Boolean { - val id = entity.id.toInt() + override operator fun contains(entity: EntityId): Boolean { + val id = entity.toInt() return id < archAndRow.size && archAndRow[id] != 0uL } - inline fun runOn(entity: Entity, run: (archetype: Archetype, row: Int) -> T): T { + inline fun runOn(entity: EntityId, run: (archetype: Archetype, row: Int) -> T): T { val info = getArchAndRow(entity) return run(archList[(info shr 32).toInt()], info.toInt()) } + + fun getType(entity: EntityId): EntityType = runOn(entity) { archetype, _ -> + archetype.type + } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/Family2ObjectArrayMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/Family2ObjectArrayMap.kt index 4810fbaf8..fdfd37e70 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/Family2ObjectArrayMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/Family2ObjectArrayMap.kt @@ -4,7 +4,7 @@ import com.mineinabyss.geary.datatypes.* import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.helpers.hasRelationKind import com.mineinabyss.geary.helpers.hasRelationTarget -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.components.ReservedComponents /** * A map of [ComponentId]s to Arrays of objects with the ability to make fast queries based on component IDs. @@ -46,8 +46,8 @@ internal class Family2ObjectArrayMap( // See componentMap definition for relations if (id.isRelation()) { val relation = Relation.of(id) - set(Relation.of(relation.kind, geary.components.any).id) - set(Relation.of(geary.components.any, relation.target).id) + set(Relation.of(relation.kind, ReservedComponents.ANY).id) + set(Relation.of(ReservedComponents.ANY, relation.target).id) } set(id) } @@ -61,8 +61,8 @@ internal class Family2ObjectArrayMap( // See componentMap definition for relations if (id.isRelation()) { val relation = Relation.of(id) - clear(Relation.of(relation.kind, geary.components.any).id) - clear(Relation.of(geary.components.any, relation.target).id) + clear(Relation.of(relation.kind, ReservedComponents.ANY).id) + clear(Relation.of(ReservedComponents.ANY, relation.target).id) } clear(id) } @@ -120,7 +120,7 @@ internal class Family2ObjectArrayMap( is Family.Leaf.Component -> componentMap[family.component.toLong()]?.copy() ?: bitsOf() is Family.Leaf.AnyToTarget -> { // The bits for relationId in componentMap represent archetypes with any relations containing target - val relationId = Relation.of(geary.components.any, family.target).id + val relationId = Relation.of(ReservedComponents.ANY, family.target).id componentMap[relationId.toLong()]?.copy()?.apply { if (family.kindMustHoldData) forEachBit { index -> val type = elementTypes[index] @@ -132,7 +132,7 @@ internal class Family2ObjectArrayMap( is Family.Leaf.KindToAny -> { // The bits for relationId in componentMap represent archetypes with any relations containing kind - val relationId = Relation.of(family.kind, geary.components.any).id + val relationId = Relation.of(family.kind, ReservedComponents.ANY).id componentMap[relationId.toLong()]?.copy()?.apply { if (family.targetMustHoldData) forEachBit { index -> val type = elementTypes[index] diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt index f62e15c6a..0e67430e5 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt @@ -3,19 +3,20 @@ package com.mineinabyss.geary.datatypes.maps import co.touchlab.stately.concurrency.Synchronizable import co.touchlab.stately.concurrency.synchronize import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.engine.archetypes.Archetype class SynchronizedArrayTypeMap : ArrayTypeMap() { private val lock = Synchronizable() - override fun getArchAndRow(entity: Entity): ULong { + override fun getArchAndRow(entity: EntityId): ULong { return lock.synchronize { super.getArchAndRow(entity) } } - override fun set(entity: Entity, archetype: Archetype, row: Int) { + override fun set(entity: EntityId, archetype: Archetype, row: Int) { lock.synchronize { super.set(entity, archetype, row) } } - override fun remove(entity: Entity) = lock.synchronize { super.remove(entity) } - override fun contains(entity: Entity): Boolean = lock.synchronize { super.contains(entity) } + override fun remove(entity: EntityId) = lock.synchronize { super.remove(entity) } + override fun contains(entity: EntityId): Boolean = lock.synchronize { super.contains(entity) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt index 0ec229680..f54dc3798 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt @@ -1,15 +1,16 @@ package com.mineinabyss.geary.datatypes.maps import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.engine.archetypes.Archetype interface TypeMap { /** Updates the record of a given entity */ - operator fun set(entity: Entity, archetype: Archetype, row: Int) + operator fun set(entity: EntityId, archetype: Archetype, row: Int) /** Removes a record associated with an entity. */ - fun remove(entity: Entity) + fun remove(entity: EntityId) /** Checks if an entity has a record associated with it. */ - operator fun contains(entity: Entity): Boolean + operator fun contains(entity: EntityId): Boolean } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/ComponentProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/ComponentProvider.kt index 66a6c81ec..3df444378 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/ComponentProvider.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/ComponentProvider.kt @@ -1,9 +1,11 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.datatypes.ComponentId -import kotlin.reflect.KClass +import com.mineinabyss.geary.datatypes.HOLDS_DATA +import com.mineinabyss.geary.datatypes.NO_ROLE +import com.mineinabyss.geary.datatypes.withRole import kotlin.reflect.KClassifier -import kotlin.reflect.KType +import kotlin.reflect.typeOf interface ComponentProvider { /** @@ -12,3 +14,9 @@ interface ComponentProvider { */ fun getOrRegisterComponentIdForClass(kClass: KClassifier): ComponentId } + +inline fun ComponentProvider.id(): ComponentId = + getOrRegisterComponentIdForClass(T::class) + +inline fun ComponentProvider.idWithNullable(): ComponentId = + id().withRole(if (typeOf().isMarkedNullable) NO_ROLE else HOLDS_DATA) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt index 6b73e5fb9..1a9836e54 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt @@ -4,24 +4,26 @@ import com.mineinabyss.geary.components.CouldHaveChildren import com.mineinabyss.geary.components.KeepEmptyArchetype import com.mineinabyss.geary.components.relations.ChildOf import com.mineinabyss.geary.components.relations.InstanceOf -import com.mineinabyss.geary.datatypes.ComponentId -import com.mineinabyss.geary.helpers.componentId +import com.mineinabyss.geary.components.relations.NoInherit import com.mineinabyss.geary.observers.Observer import com.mineinabyss.geary.observers.events.* -class Components { - val any: ComponentId = componentId() - val suppressRemoveEvent = componentId() - val couldHaveChildren = componentId() - val observer = componentId() - val onAdd = componentId() - val onSet = componentId() - val onFirstSet = componentId() - val onUpdate = componentId() - val onRemove = componentId() - val onExtend = componentId() - val onEntityRemoved = componentId() - val childOf = componentId() - val instanceOf = componentId() - val keepEmptyArchetype = componentId() +class Components( + comp: ComponentProvider, +) { + val any = comp.id() + val suppressRemoveEvent = comp.id() + val couldHaveChildren = comp.id() + val observer = comp.id() + val onAdd = comp.id() + val onSet = comp.id() + val onFirstSet = comp.id() + val onUpdate = comp.id() + val onRemove = comp.id() + val onExtend = comp.id() + val onEntityRemoved = comp.id() + val childOf = comp.id() + val instanceOf = comp.id() + val noInherit = comp.id() + val keepEmptyArchetype = comp.id() } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityMutateOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityMutateOperations.kt index 2c9ec0810..ef5e587c3 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityMutateOperations.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityMutateOperations.kt @@ -3,6 +3,7 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.datatypes.Component import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId interface EntityMutateOperations { /** @@ -11,24 +12,24 @@ interface EntityMutateOperations { * @param noEvent Whether to fire an [AddedComponent] event. */ fun setComponentFor( - entity: Entity, + entity: EntityId, componentId: ComponentId, data: Component, noEvent: Boolean ) /** Adds this [componentId] to the [entity]'s type but doesn't store any data. */ - fun addComponentFor(entity: Entity, componentId: ComponentId, noEvent: Boolean) + fun addComponentFor(entity: EntityId, componentId: ComponentId, noEvent: Boolean) - fun extendFor(entity: Entity, base: Entity) + fun extendFor(entity: EntityId, base: EntityId) /** Removes a [componentId] from an [entity] and clears any data previously associated with it. */ - fun removeComponentFor(entity: Entity, componentId: ComponentId, noEvent: Boolean): Boolean + fun removeComponentFor(entity: EntityId, componentId: ComponentId, noEvent: Boolean): Boolean // To avoid breaking changes from component remove events, marked for removal @Deprecated("Use removeComponentFor(entity, componentId, noEvent) instead.") - fun removeComponentFor(entity: Entity, componentId: ComponentId): Boolean + fun removeComponentFor(entity: EntityId, componentId: ComponentId): Boolean /** Removes all components from an entity. */ - fun clearEntity(entity: Entity) + fun clearEntity(entity: EntityId) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityProvider.kt index f112f2dc6..a9467b51b 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityProvider.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityProvider.kt @@ -1,15 +1,10 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.EntityType interface EntityProvider { /** Creates a new entity. */ - fun create(): Entity - - /** Removes an entity, freeing up its entity id for later reuse. */ - fun remove(entity: Entity) - - /** Gets an [entity]'s type */ - fun getType(entity: Entity): EntityType + fun create(): EntityId } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityReadOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityReadOperations.kt index 30c9a2a1f..f483f0b4c 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityReadOperations.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityReadOperations.kt @@ -5,28 +5,28 @@ import com.mineinabyss.geary.systems.accessors.RelationWithData interface EntityReadOperations { /** Gets a [componentId]'s data from an [entity] or null if not present/the component doesn't hold any data. */ - fun getComponentFor(entity: Entity, componentId: ComponentId): Component? + fun get(entity: EntityId, componentId: ComponentId): Component? /** Gets a list of all the components [entity] has, as well as relations in the form of [RelationComponent]. */ - fun getComponentsFor(entity: Entity): Array + fun getAll(entity: EntityId): Array /** Checks whether an [entity] is still active in the engine. */ - fun exists(entity: Entity): Boolean + fun exists(entity: EntityId): Boolean /** * Gets relations in the same format as [Archetype.getRelations], but when kind/target [HOLDS_DATA], the appropriate * data is written to a [RelationWithData] object. */ fun getRelationsWithDataFor( - entity: Entity, + entity: EntityId, kind: ComponentId, target: EntityId ): List> - fun getRelationsFor(entity: Entity, kind: ComponentId, target: EntityId): List + fun getRelationsFor(entity: EntityId, kind: ComponentId, target: EntityId): List /** Checks whether an [entity] has a [componentId] */ - fun hasComponentFor(entity: Entity, componentId: ComponentId): Boolean + fun has(entity: EntityId, componentId: ComponentId): Boolean } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/PipelineImpl.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/PipelineImpl.kt index 290c356fb..dc88d5311 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/PipelineImpl.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/PipelineImpl.kt @@ -7,9 +7,9 @@ import com.mineinabyss.geary.systems.System import com.mineinabyss.geary.systems.TrackedSystem import com.mineinabyss.geary.systems.query.Query -class PipelineImpl : Pipeline { - private val queryManager get() = geary.queryManager - +class PipelineImpl( + val queryManager: QueryManager +) : Pipeline { private val onSystemAdd = mutableListOf<(System<*>) -> Unit>() private val repeatingSystems: MutableSet> = mutableSetOf() diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/QueryManager.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/QueryManager.kt index e5ec5d34c..60b41289a 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/QueryManager.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/QueryManager.kt @@ -1,6 +1,8 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId +import com.mineinabyss.geary.datatypes.EntityIdArray import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.systems.query.CachedQuery import com.mineinabyss.geary.systems.query.Query @@ -9,12 +11,14 @@ interface QueryManager { fun trackQuery(query: T): CachedQuery /** Returns a list of entities matching the given family. */ - fun getEntitiesMatching(family: Family): List + fun getEntitiesMatching(family: Family): EntityIdArray /** * Returns a sequence of entities matching the given family. * This should be faster than [getEntitiesMatching] but will depend on impl. * In an archetypal engine, this gets all matching archetypes first, then maps them to entities as a sequence. */ - fun getEntitiesMatchingAsSequence(family: Family): Sequence + fun getEntitiesMatchingAsSequence(family: Family): Sequence + + fun childrenOf(parent: EntityId): EntityIdArray } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt index 25ed09f51..f4baf7ff5 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt @@ -1,12 +1,10 @@ package com.mineinabyss.geary.engine.archetypes import androidx.collection.* -import com.mineinabyss.geary.components.relations.InstanceOf -import com.mineinabyss.geary.components.relations.NoInherit +import com.mineinabyss.geary.components.ReservedComponents import com.mineinabyss.geary.datatypes.* +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap import com.mineinabyss.geary.helpers.* -import com.mineinabyss.geary.modules.archetypes -import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.observers.events.* import com.mineinabyss.geary.systems.accessors.RelationWithData @@ -19,19 +17,12 @@ import com.mineinabyss.geary.systems.accessors.RelationWithData */ class Archetype internal constructor( val type: EntityType, - var id: Int + var id: Int, + private val records: ArrayTypeMap, + private val archetypeProvider: ArchetypeProvider, ) { - private val records = archetypes.records - private val archetypeProvider = archetypes.archetypeProvider - private val eventRunner = archetypes.eventRunner - private val comps get() = geary.components - - val entities: Sequence - get() { - val entities = mutableListOf() - ids.forEach { entities += it.toGeary() } - return entities.asSequence() - } + val entities: EntityIdArray + get() = ULongArray(size) { id -> ids[id].toULong() } /** The entity ids in this archetype. Indices are the same as [componentData]'s sub-lists. */ private val ids = mutableLongListOf() @@ -47,7 +38,7 @@ class Archetype internal constructor( /** Component ids in the type that are to hold data */ // Currently all relations must hold data and the HOLDS_DATA bit on them corresponds to the component part. - private val dataHoldingType: EntityType = type.filter { it.holdsData() } + internal val dataHoldingType: EntityType = type.filter { it.holdsData() } /** An outer list with indices for component ids, and sub-lists with data indexed by entity [ids]. */ internal val componentData: Array> = @@ -74,8 +65,8 @@ class Archetype internal constructor( val size: Int get() = ids.size // ==== Helper functions ==== - fun getEntity(row: Int): Entity { - return ids[row].toGeary() + fun getEntity(row: Int): EntityId { + return ids[row].toULong() } /** @@ -94,7 +85,7 @@ class Archetype internal constructor( return componentData[compIndex][row] } - private fun getUnsafe(row: Int, componentId: ComponentId): Component { + internal fun getUnsafe(row: Int, componentId: ComponentId): Component { val compIndex = indexOf(componentId) return componentData[compIndex][row] } @@ -156,7 +147,7 @@ class Archetype internal constructor( private fun moveOnlyAdding( oldArc: Archetype, oldRow: Int, - entity: EntityId + entity: EntityId, ) = move(entity) { for (i in 0..componentData.lastIndex) { componentData[i].add(oldArc.componentData[i][oldRow]) @@ -192,14 +183,14 @@ class Archetype internal constructor( } - internal fun createWithoutData(entity: Entity): Int { - ids.add(entity.idL) + internal fun createWithoutData(entity: EntityId): Int { + ids.add(entity.toLong()) return ids.lastIndex } internal inline fun move( entity: EntityId, - copyData: () -> Unit + copyData: () -> Unit, ): Int { if (unregistered) error("Tried adding entity to archetype that is no longer registered. Was it referenced outside of Geary?") ids.add(entity.toLong()) @@ -207,19 +198,13 @@ class Archetype internal constructor( copyData() - records[Entity(entity), this] = row + records[entity, this] = row return row } // For the following few functions, both entity and row are passed to avoid doing several array look-ups // (ex when set calls remove). - internal fun addComponent( - row: Int, - componentId: ComponentId, - callEvent: Boolean, - ) = addComponent(row, componentId, callEvent) { _, _ -> } - /** * Add a [componentId] to an entity represented by [record], moving it to the appropriate archetype. * @@ -228,8 +213,7 @@ class Archetype internal constructor( internal inline fun addComponent( row: Int, componentId: ComponentId, - callEvent: Boolean, - onUpdated: (Archetype, row: Int) -> Unit + onUpdated: (Archetype, row: Int) -> Unit = { _, _ -> }, ) { // if already present in this archetype, stop here since we don't need to update any data if (contains(componentId)) return @@ -240,17 +224,9 @@ class Archetype internal constructor( val newRow = moveTo.moveOnlyAdding(this, row, entityId) removeEntity(row) - if (callEvent) moveTo.callComponentModifyEvent(comps.onAdd, componentId, newRow, onUpdated) - else onUpdated(moveTo, newRow) + onUpdated(moveTo, newRow) } - internal fun setComponent( - row: Int, - componentId: ComponentId, - data: Component, - callEvent: Boolean, - ) = setComponent(row, componentId, data, callEvent) { _, _ -> } - /** * Sets [data] at a [componentId] for an entity, moving it to the appropriate archetype. * Will ensure this component without [HOLDS_DATA] is always present. @@ -261,8 +237,7 @@ class Archetype internal constructor( row: Int, componentId: ComponentId, data: Component, - callEvent: Boolean, - onUpdated: (Archetype, row: Int) -> Unit + onUpdated: (firstSet: Boolean, Archetype, row: Int) -> Unit = { _, _, _ -> }, ) { val dataComponent = componentId.withRole(HOLDS_DATA) @@ -270,12 +245,7 @@ class Archetype internal constructor( val addIndex = indexOf(dataComponent) if (addIndex >= 0) { componentData[addIndex][row] = data - if (callEvent) { - callComponentModifyEvent(comps.onUpdate, componentId, row) { arch, row -> - // Potential archetype modification can occur after event - arch.callComponentModifyEvent(comps.onSet, componentId, row, onUpdated) - } - } + onUpdated(false, this, row) return } @@ -286,69 +256,7 @@ class Archetype internal constructor( removeEntity(row) // Component add listeners must query the target, this is an optimization - if (callEvent) { - moveTo.callComponentModifyEvent(comps.onSet, componentId, newRow) { arch, row -> - arch.callComponentModifyEvent(comps.onFirstSet, componentId, row, onUpdated) - } - } - else onUpdated(moveTo, newRow) - } - - private inline fun callComponentModifyEvent( - eventType: ComponentId, - involvedComp: ComponentId, - row: Int, - onComplete: (Archetype, row: Int) -> Unit = { _, _ -> } - ) { - val entity = getEntity(row) - callComponentModifyEvent(eventType, involvedComp, row) - // Don't have any way to know final archetype and row without re-reading - records.runOn(entity, onComplete) - } - - private fun callComponentModifyEvent(eventType: ComponentId, involvedComp: ComponentId, row: Int) { - val entity = getEntity(row) - eventRunner.callEvent(eventType, null, involvedComp, entity) - } - - @Suppress("NAME_SHADOWING") // Want to make sure original arch/row is not accidentally accessed - fun instantiateTo( - baseRow: Int, - instanceArch: Archetype, - instanceRow: Int, - callEvent: Boolean = true, - ) { - val baseEntity = this.getEntity(baseRow) - val instanceEntity = instanceArch.getEntity(instanceRow) - var instanceArch = instanceArch - var instanceRow = instanceRow - instanceArch.addComponent(instanceRow, Relation.of(baseEntity).id, true) { arch, row -> - instanceArch = arch; instanceRow = row - } - - val noInheritComponents = getRelationsByKind(componentId()).map { Relation.of(it).target } - type.filter { !it.holdsData() && it !in noInheritComponents }.forEach { - instanceArch.addComponent(instanceRow, it, true) { arch, row -> instanceArch = arch; instanceRow = row } - } - dataHoldingType.forEach { - if (it.withoutRole(HOLDS_DATA) in noInheritComponents) return@forEach - instanceArch.setComponent(instanceRow, it, getUnsafe(baseRow, it), true) { arch, row -> - instanceArch = arch; instanceRow = row - } - } - baseEntity.children.fastForEach { - it.addParent(instanceEntity) - } - records.runOn(instanceEntity) { arch, row -> instanceArch = arch; instanceRow = row } - - if (callEvent) { - eventRunner.callEvent( - comps.onExtend, - OnExtend(baseEntity), - NO_COMPONENT, - instanceArch.getEntity(instanceRow) - ) - } + onUpdated(true, moveTo, newRow) } /** @@ -359,45 +267,40 @@ class Archetype internal constructor( internal fun removeComponent( row: Int, component: ComponentId, - callEvent: Boolean, + onModify: (Archetype, row: Int, onComplete: (Archetype, Int) -> Unit) -> Unit = { a, r, onComplete -> + onComplete(a, r) + }, ): Boolean { val entityId = ids[row].toULong() if (component !in type) return false - if (callEvent) { - // Call event first, then ensure component is removed - callComponentModifyEvent(comps.onRemove, component, row) { finalArch, finalRow -> - if (component !in finalArch.type) return true // Case where listeners manually removed component - val moveTo = finalArch - component - moveTo.moveWithoutComponent(finalArch, finalRow, component, entityId) - finalArch.removeEntity(row) - } - return true + // We run onModify before the component is removed, since observers will be interested in the component data, + // Then take the result archetype after onModify and ensure the component is removed from there + onModify(this, row) { finalArch, finalRow -> + val moveTo = finalArch - component + moveTo.moveWithoutComponent(finalArch, finalRow, component, entityId) + finalArch.removeEntity(finalRow) } - - val moveTo = this - component - - moveTo.moveWithoutComponent(this, row, component, entityId) - removeEntity(row) return true } - private fun unregisterIfEmpty() { - if (allowUnregister == FALSE) return - if (ids.size == 0 && type.size != 0 && componentAddEdges.size == 0) { - if (allowUnregister == UNKNOWN) allowUnregister = - if (type.contains(comps.keepEmptyArchetype)) FALSE else TRUE - if (allowUnregister == FALSE) return - archetypes.queryManager.unregisterArchetype(this) - unregistered = true - componentRemoveEdges.forEach { id, archetype -> - archetype.componentAddEdges.remove(id.toLong()) - archetype.unregisterIfEmpty() - } - componentRemoveEdges.clear() - } - } +// TODO reimplement +// private fun unregisterIfEmpty() { +// if (allowUnregister == FALSE) return +// if (ids.size == 0 && type.size != 0 && componentAddEdges.size == 0) { +// if (allowUnregister == UNKNOWN) allowUnregister = +// if (type.contains(comps.keepEmptyArchetype)) FALSE else TRUE +// if (allowUnregister == FALSE) return +// queryManager.unregisterArchetype(this) +// unregistered = true +// componentRemoveEdges.forEach { id, archetype -> +// archetype.componentAddEdges.remove(id.toLong()) +// archetype.unregisterIfEmpty() +// } +// componentRemoveEdges.clear() +// } +// } /** Gets all the components associated with an entity at a [row]. */ internal fun getComponents(row: Int, add: Pair? = null): Array { @@ -424,8 +327,8 @@ class Archetype internal constructor( * All other roles are ignored for the [target]. */ internal fun getRelations(kind: ComponentId, target: EntityId): List { - val specificKind = kind and ENTITY_MASK != comps.any - val specificTarget = target and ENTITY_MASK != comps.any + val specificKind = kind and ENTITY_MASK != ReservedComponents.ANY + val specificTarget = target and ENTITY_MASK != ReservedComponents.ANY return when { specificKind && specificTarget -> listOf(Relation.of(kind, target)) specificTarget -> getRelationsByTarget(target).map { Relation.of(it) } @@ -442,7 +345,7 @@ class Archetype internal constructor( row: Int, kind: ComponentId, target: EntityId, - relations: List + relations: List, ): List> { return relations.map { relation -> RelationWithData( @@ -466,7 +369,7 @@ class Archetype internal constructor( val replacement = ids[lastIndex] ids[row] = replacement componentData.fastForEach { it[row] = it.last() } - records[replacement.toGeary(), this@Archetype] = row + records[replacement.toULong(), this@Archetype] = row } @@ -477,7 +380,7 @@ class Archetype internal constructor( if (index != -1) ids.removeAt(index) componentData.fastForEach { it.removeAt(lastIndex) } - unregisterIfEmpty() +// unregisterIfEmpty() TODO reimplement } override fun equals(other: Any?): Boolean { diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEngine.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEngine.kt index a61d5399f..f7ce300d1 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEngine.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEngine.kt @@ -1,9 +1,9 @@ package com.mineinabyss.geary.engine.archetypes +import co.touchlab.kermit.Logger import com.mineinabyss.geary.datatypes.* import com.mineinabyss.geary.engine.* import com.mineinabyss.geary.helpers.fastForEach -import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.systems.TrackedSystem import com.mineinabyss.geary.systems.query.Query import kotlinx.coroutines.* @@ -18,12 +18,12 @@ import kotlin.time.Duration * * Learn more [here](https://github.com/MineInAbyss/Geary/wiki/Basic-ECS-engine-architecture). */ -open class ArchetypeEngine(override val tickDuration: Duration) : TickingEngine() { - private val pipeline get() = geary.pipeline - private val logger get() = geary.logger - - override val coroutineContext: CoroutineContext = - (CoroutineScope(Dispatchers.Default) + CoroutineName("Geary Engine")).coroutineContext +open class ArchetypeEngine( + private val pipeline: Pipeline, + private val logger: Logger, + override val tickDuration: Duration, + override val coroutineContext: CoroutineContext +) : TickingEngine() { /** Describes how to individually tick each system */ protected open fun TrackedSystem.runSystem() { diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeQueryManager.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeQueryManager.kt index 32fff305d..3160a6c00 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeQueryManager.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeQueryManager.kt @@ -2,8 +2,11 @@ package com.mineinabyss.geary.engine.archetypes import co.touchlab.stately.concurrency.Synchronizable import co.touchlab.stately.concurrency.synchronize -import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.components.ReservedComponents +import com.mineinabyss.geary.datatypes.EntityId +import com.mineinabyss.geary.datatypes.EntityIdArray import com.mineinabyss.geary.datatypes.family.Family +import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.datatypes.maps.Family2ObjectArrayMap import com.mineinabyss.geary.engine.QueryManager import com.mineinabyss.geary.helpers.contains @@ -48,13 +51,21 @@ class ArchetypeQueryManager : QueryManager { return archetypes.match(family) } - override fun getEntitiesMatching(family: Family): List { - return getArchetypesMatching(family).flatMap(Archetype::entities) + override fun getEntitiesMatching(family: Family): EntityIdArray { + val archetypes = getArchetypesMatching(family) + // TODO avoid the list creation here, make the ULongArray directly + return archetypes.flatMap(Archetype::entities).toULongArray() } - override fun getEntitiesMatchingAsSequence(family: Family): Sequence { + override fun getEntitiesMatchingAsSequence(family: Family): Sequence { return getArchetypesMatching(family) .asSequence() .flatMap(Archetype::entities) } + + override fun childrenOf(parent: EntityId): EntityIdArray { + return getEntitiesMatching(family { + hasRelation(ReservedComponents.CHILD_OF, parent) + }) + } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ComponentAsEntityProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ComponentAsEntityProvider.kt index 670819a69..605a782d1 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ComponentAsEntityProvider.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ComponentAsEntityProvider.kt @@ -1,41 +1,43 @@ package com.mineinabyss.geary.engine.archetypes -import co.touchlab.stately.concurrency.Synchronizable -import co.touchlab.stately.concurrency.synchronize +import co.touchlab.kermit.Logger import com.mineinabyss.geary.components.ComponentInfo +import com.mineinabyss.geary.components.ReservedComponents import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.engine.ComponentProvider -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.engine.EntityProvider import kotlin.reflect.KClassifier -class ComponentAsEntityProvider : ComponentProvider { - private val entityProvider get() = geary.entityProvider - private val logger get() = geary.logger - +class ComponentAsEntityProvider( + val entityProvider: EntityProvider, + val logger: Logger, +) : ComponentProvider { private val classToComponentMap = mutableMapOf() - private val classToComponentMapLock = Synchronizable() +// private val classToComponentMapLock = Synchronizable() TODO async support necessary? - internal fun createComponentInfo() { - logger.v("Registering ComponentInfo component") - //Register an entity for the ComponentInfo component, otherwise getComponentIdForClass does a StackOverflow - val componentInfo = entityProvider.create() - classToComponentMap[ComponentInfo::class] = componentInfo.id.toLong() - componentInfo.set(ComponentInfo(ComponentInfo::class), noEvent = true) + init { + createReservedComponents() } - override fun getOrRegisterComponentIdForClass(kClass: KClassifier): ComponentId = - classToComponentMapLock.synchronize { - val id = classToComponentMap.getOrElse(kClass) { - return@synchronize registerComponentIdForClass(kClass) - } - return@synchronize id.toULong() + override fun getOrRegisterComponentIdForClass(kClass: KClassifier): ComponentId { + val id = classToComponentMap.getOrElse(kClass) { + return registerComponentIdForClass(kClass) } + return id.toULong() + } private fun registerComponentIdForClass(kClass: KClassifier): ComponentId { logger.v("Registering new component: $kClass") val compEntity = entityProvider.create() - compEntity.set(ComponentInfo(kClass), noEvent = true) - classToComponentMap[kClass] = compEntity.id.toLong() - return compEntity.id +// compEntity.set(ComponentInfo(kClass), noEvent = true) + classToComponentMap[kClass] = compEntity.toLong() + return compEntity + } + + private fun createReservedComponents() { + logger.v("Creating reserved components") + ReservedComponents.reservedComponents.forEach { (kClass, id) -> + classToComponentMap[kClass] = id.toLong() + } } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt index 3cd546e42..3e688d50a 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt @@ -1,72 +1,80 @@ package com.mineinabyss.geary.engine.archetypes import co.touchlab.stately.concurrency.AtomicLong -import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.EntityStack -import com.mineinabyss.geary.datatypes.EntityType -import com.mineinabyss.geary.datatypes.GearyEntity +import com.mineinabyss.geary.datatypes.Relation import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap +import com.mineinabyss.geary.engine.Components import com.mineinabyss.geary.engine.EntityProvider -import com.mineinabyss.geary.helpers.* -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.observers.events.OnEntityRemoved +import com.mineinabyss.geary.engine.archetypes.operations.ArchetypeMutateOperations +import com.mineinabyss.geary.engine.archetypes.operations.ArchetypeReadOperations +import com.mineinabyss.geary.helpers.NO_COMPONENT +import com.mineinabyss.geary.components.ReservedComponents +import com.mineinabyss.geary.observers.EventRunner -class EntityByArchetypeProvider( - private val reuseIDsAfterRemoval: Boolean = true, -) : EntityProvider { - private lateinit var records: ArrayTypeMap - - // private val archetypeProvider: ArchetypeProvider by lazy { archetypes.archetypeProvider } - private lateinit var root: Archetype - - private val removedEntities: EntityStack = EntityStack() - private val currId = AtomicLong(0L) - - override fun create(): GearyEntity { - val entity: GearyEntity = if (reuseIDsAfterRemoval) { - removedEntities.popOrElse { (currId.incrementAndGet() - 1).toGeary() } - } else (currId.incrementAndGet() - 1).toGeary() - - createRecord(entity) - return entity - } - - override fun remove(entity: Entity) { - if (!entity.has(geary.components.suppressRemoveEvent)) - entity.emit(geary.components.onEntityRemoved, OnEntityRemoved(), NO_COMPONENT) +class EntityRemove( + private val entityProvider: EntityByArchetypeProvider, + private val reader: ArchetypeReadOperations, + private val write: ArchetypeMutateOperations, + private val records: ArrayTypeMap, + private val components: Components, + private val eventRunner: EventRunner, + private val queryManager: ArchetypeQueryManager, +) { + /** Removes an entity, freeing up its entity id for later reuse. */ + fun remove(entity: EntityId) { + if (!reader.has(entity, components.suppressRemoveEvent)) + eventRunner.callEvent(components.onEntityRemoved, null, NO_COMPONENT, entity) // remove all children of this entity from the ECS as well - if (entity.has(geary.components.couldHaveChildren)) entity.apply { - children.fastForEach { + if (reader.has(entity, components.couldHaveChildren)) entity.apply { + queryManager.childrenOf(entity).forEach { + val parents = reader.parentsOf(it) // Remove self from the child's parents or remove the child if it no longer has parents - if (it.parents == setOf(this)) it.removeEntity() - else it.removeParent(this) + if (parents == ulongArrayOf(this)) remove(it) + else write.removeComponentFor(it, Relation.of(components.childOf, this).id, false) } } // Emit remove events for each component (they get cleared all at once after this) - entity.type.forEach { compId -> - if (entity.has(compId)) entity.emit(event = geary.components.onRemove, involving = compId) + records.getType(entity).forEach { compId -> + if (reader.has(entity, compId)) + eventRunner.callEvent(components.onRemove, null, compId, entity) } records.runOn(entity) { archetype, row -> archetype.removeEntity(row) records.remove(entity) - removedEntities.push(entity) + entityProvider.removedEntities.push(entity) } } +} + +class EntityByArchetypeProvider( + private val reuseIDsAfterRemoval: Boolean = true, + private val archetypeProvider: ArchetypeProvider, + private val records: ArrayTypeMap, +) : EntityProvider { + internal val removedEntities: EntityStack = EntityStack() + private val currId = AtomicLong(0L) - fun init(records: ArrayTypeMap, root: Archetype) { - this.records = records - this.root = root + init { + // Allocate reserved components + repeat(ReservedComponents.reservedComponents.size) { create() } } - override fun getType(entity: Entity): EntityType = records.runOn(entity) { archetype, _ -> - archetype.type + override fun create(): EntityId { + val entity: EntityId = if (reuseIDsAfterRemoval) { + removedEntities.popOrElse { (currId.incrementAndGet() - 1).toULong() } + } else (currId.incrementAndGet() - 1).toULong() + + createRecord(entity) + return entity } - private fun createRecord(entity: Entity) { - val root = root + private fun createRecord(entity: EntityId) { + val root = archetypeProvider.rootArchetype val row = root.createWithoutData(entity) records[entity, root] = row } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt index 28960e6a8..044e0dd8c 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt @@ -6,13 +6,14 @@ import co.touchlab.stately.concurrency.Synchronizable import co.touchlab.stately.concurrency.synchronize import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.EntityType -import com.mineinabyss.geary.modules.archetypes - -class SimpleArchetypeProvider : ArchetypeProvider { - private val queryManager: ArchetypeQueryManager get() = archetypes.queryManager +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap +class SimpleArchetypeProvider( + private val records: ArrayTypeMap, + private val queryManager: ArchetypeQueryManager, +) : ArchetypeProvider { override val rootArchetype: Archetype by lazy { - Archetype(EntityType(), 0).also { + createArchetype(EntityType(), 0).also { queryManager.registerArchetype(it) } } @@ -21,14 +22,22 @@ class SimpleArchetypeProvider : ArchetypeProvider { private fun createArchetype(prevNode: Archetype, componentEdge: ComponentId): Archetype { - val arc = Archetype(prevNode.type.plus(componentEdge), queryManager.archetypeCount) - + val arc = createArchetype(prevNode.type.plus(componentEdge), queryManager.archetypeCount) arc.componentRemoveEdges[componentEdge.toLong()] = prevNode prevNode.componentAddEdges[componentEdge.toLong()] = arc queryManager.registerArchetype(arc) return arc } + private fun createArchetype(type: EntityType, id: Int): Archetype { + return Archetype( + type = type, + id = id, + records = records, + archetypeProvider = this, + ) + } + override fun getArchetype(entityType: EntityType): Archetype = archetypeWriteLock.synchronize { var node = rootArchetype entityType.forEach { compId -> diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt index d5d5025de..466199fd8 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt @@ -2,68 +2,175 @@ package com.mineinabyss.geary.engine.archetypes.operations import com.mineinabyss.geary.datatypes.* import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap +import com.mineinabyss.geary.engine.Components import com.mineinabyss.geary.engine.EntityMutateOperations +import com.mineinabyss.geary.engine.QueryManager +import com.mineinabyss.geary.engine.archetypes.Archetype import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider -import com.mineinabyss.geary.modules.archetypes - -class ArchetypeMutateOperations : EntityMutateOperations { - private lateinit var records: ArrayTypeMap - private val archetypeProvider: ArchetypeProvider get() = archetypes.archetypeProvider +import com.mineinabyss.geary.helpers.NO_COMPONENT +import com.mineinabyss.geary.observers.EventRunner +import com.mineinabyss.geary.observers.events.OnExtend +class ArchetypeMutateOperations( + private val archetypeProvider: ArchetypeProvider, + private val records: ArrayTypeMap, + private val components: Components, + private val eventRunner: EventRunner, + private val queryManager: QueryManager, +) : EntityMutateOperations { override fun setComponentFor( - entity: Entity, + entity: EntityId, componentId: ComponentId, data: Component, - noEvent: Boolean + noEvent: Boolean, ) { records.runOn(entity) { archetype, row -> // Only add HOLDS_DATA if this isn't a relation. All relations implicitly hold data currently and that bit // corresponds to the component part of the relation. val componentWithRole = componentId.withRole(HOLDS_DATA) - archetype.setComponent(row, componentWithRole, data, !noEvent) + setComponent(archetype, row, componentWithRole, data, !noEvent) } } override fun addComponentFor( - entity: Entity, + entity: EntityId, componentId: ComponentId, - noEvent: Boolean + noEvent: Boolean, ) { records.runOn(entity) { archetype, row -> - archetype.addComponent(row, componentId.withoutRole(HOLDS_DATA), !noEvent) + addComponent(archetype, row, componentId.withoutRole(HOLDS_DATA), !noEvent) } } - override fun extendFor(entity: Entity, base: Entity) { + override fun extendFor(entity: EntityId, base: EntityId) { records.runOn(base) { archetype, row -> records.runOn(entity) { entityArch, entityRow -> - archetype.instantiateTo(row, entityArch, entityRow) + instantiateTo(archetype, row, entityArch, entityRow) } } } - override fun removeComponentFor(entity: Entity, componentId: ComponentId, noEvent: Boolean): Boolean { + @Suppress("NAME_SHADOWING") // Want to make sure original arch/row is not accidentally accessed + private fun instantiateTo( + baseArchetype: Archetype, + baseRow: Int, + instanceArch: Archetype, + instanceRow: Int, + callEvent: Boolean = true, + ) { + val baseEntity = baseArchetype.getEntity(baseRow) + val instanceEntity = instanceArch.getEntity(instanceRow) + var instanceArch = instanceArch + var instanceRow = instanceRow + + addComponent(instanceArch, instanceRow, Relation.of(components.instanceOf, baseEntity).id, true) { arch, row -> + instanceArch = arch; instanceRow = row + } + + val noInheritComponents = baseArchetype.getRelationsByKind(components.noInherit).map { Relation.of(it).target } + baseArchetype.type.filter { !it.holdsData() && it !in noInheritComponents }.forEach { + addComponent(instanceArch, instanceRow, it, true) { arch, row -> instanceArch = arch; instanceRow = row } + } + baseArchetype.dataHoldingType.forEach { + if (it.withoutRole(HOLDS_DATA) in noInheritComponents) return@forEach + setComponent(instanceArch, instanceRow, it, baseArchetype.getUnsafe(baseRow, it), true) { arch, row -> + instanceArch = arch; instanceRow = row + } + } + + queryManager.childrenOf(baseEntity).forEach { child -> + // Add instanceEntity as parent + addComponentFor(instanceEntity, components.couldHaveChildren, true) + addComponentFor(child, Relation.of(components.childOf, instanceEntity).id, false) + } + records.runOn(instanceEntity) { arch, row -> instanceArch = arch; instanceRow = row } + + if (callEvent) eventRunner.callEvent( + components.onExtend, + OnExtend(baseEntity), + NO_COMPONENT, + instanceArch.getEntity(instanceRow) + ) + } + + override fun removeComponentFor(entity: EntityId, componentId: ComponentId, noEvent: Boolean): Boolean { val a = records.runOn(entity) { archetype, row -> - archetype.removeComponent(row, componentId.withRole(HOLDS_DATA), !noEvent) + archetype.removeComponent(row, componentId.withRole(HOLDS_DATA), onModify = { moveTo, newRow, onComplete -> + if (!noEvent) callComponentModifyEvent(moveTo, components.onRemove, componentId, newRow, onComplete) + }) } val b = records.runOn(entity) { archetype, row -> - archetype.removeComponent(row, componentId.withoutRole(HOLDS_DATA), !noEvent) + archetype.removeComponent( + row, + componentId.withoutRole(HOLDS_DATA), + onModify = { moveTo, newRow, onComplete -> + if (!noEvent) callComponentModifyEvent(moveTo, components.onRemove, componentId, newRow, onComplete) + }) } return a || b // return whether anything was changed } - override fun removeComponentFor(entity: Entity, componentId: ComponentId): Boolean = + @Deprecated("Use removeComponentFor(entity, componentId, noEvent) instead.") + override fun removeComponentFor(entity: EntityId, componentId: ComponentId): Boolean = removeComponentFor(entity, componentId, false) - override fun clearEntity(entity: Entity) { + override fun clearEntity(entity: EntityId) { records.runOn(entity) { archetype, row -> archetype.removeEntity(row) val newRow = archetypeProvider.rootArchetype.createWithoutData(entity) - records[entity, archetypes.archetypeProvider.rootArchetype] = newRow + records[entity, archetypeProvider.rootArchetype] = newRow } } - fun init(records: ArrayTypeMap) { - this.records = records + private inline fun callComponentModifyEvent( + archetype: Archetype, + eventType: ComponentId, + involvedComp: ComponentId, + row: Int, + onComplete: (Archetype, row: Int) -> Unit = { _, _ -> }, + ) { + val entity = archetype.getEntity(row) + callComponentModifyEvent(archetype, eventType, involvedComp, row) + // Don't have any way to know final archetype and row without re-reading + records.runOn(entity, onComplete) + } + + private fun callComponentModifyEvent( + archetype: Archetype, + eventType: ComponentId, + involvedComp: ComponentId, + row: Int, + ) { + val entity = archetype.getEntity(row) + eventRunner.callEvent(eventType, null, involvedComp, entity) + } + + private inline fun addComponent( + archetype: Archetype, + row: Int, + componentId: ComponentId, + callEvent: Boolean, + onUpdated: (Archetype, row: Int) -> Unit = { _, _ -> }, + ) { + archetype.addComponent(row, componentId, onUpdated = { moveTo, newRow -> + if (callEvent) callComponentModifyEvent(moveTo, components.onAdd, componentId, newRow, onUpdated) + }) + } + + @Suppress("NAME_SHADOWING") // Want to make sure original arch/row is not accidentally accessed + private inline fun setComponent( + archetype: Archetype, + row: Int, + componentId: ComponentId, + data: Component, + callEvent: Boolean, + onUpdated: (Archetype, row: Int) -> Unit = { _, _ -> }, + ) { + archetype.setComponent(row, componentId, data, onUpdated = { firstSet, archetype, row -> + if (callEvent) callComponentModifyEvent(archetype, components.onSet, componentId, row) { archetype, row -> + if (firstSet) callComponentModifyEvent(archetype, components.onFirstSet, componentId, row, onUpdated) + else onUpdated(archetype, row) + } else onUpdated(archetype, row) + }) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt index 0c4dc5876..5cc91093f 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt @@ -1,20 +1,22 @@ package com.mineinabyss.geary.engine.archetypes.operations import com.mineinabyss.geary.datatypes.* +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap +import com.mineinabyss.geary.engine.Components import com.mineinabyss.geary.engine.EntityReadOperations -import com.mineinabyss.geary.modules.archetypes import com.mineinabyss.geary.systems.accessors.RelationWithData -class ArchetypeReadOperations : EntityReadOperations { - private val records get() = archetypes.records - - override fun getComponentFor(entity: Entity, componentId: ComponentId): Component? { +class ArchetypeReadOperations( + val components: Components, + val records: ArrayTypeMap, +) : EntityReadOperations { + override fun get(entity: EntityId, componentId: ComponentId): Component? { records.runOn(entity) { archetype, row -> return archetype[row, componentId.let { if (it.hasRole(RELATION)) it else it.withRole(HOLDS_DATA) }] } } - override fun getComponentsFor(entity: Entity): Array { + override fun getAll(entity: EntityId): Array { records.runOn(entity) { archetype, row -> return archetype.getComponents(row).also { array -> archetype.relationsWithData.forEach { relation -> @@ -25,21 +27,28 @@ class ArchetypeReadOperations : EntityReadOperations { } } - override fun exists(entity: Entity): Boolean { + override fun exists(entity: EntityId): Boolean { return records.contains(entity) } override fun getRelationsWithDataFor( - entity: Entity, + entity: EntityId, kind: ComponentId, target: EntityId, ): List> = records.runOn(entity) { archetype, row -> return archetype.readRelationDataFor(row, kind, target, archetype.getRelations(kind, target)) } - override fun getRelationsFor(entity: Entity, kind: ComponentId, target: EntityId): List = + override fun getRelationsFor(entity: EntityId, kind: ComponentId, target: EntityId): List = records.runOn(entity) { archetype, _ -> archetype.getRelations(kind, target) } - override fun hasComponentFor(entity: Entity, componentId: ComponentId): Boolean = + override fun has(entity: EntityId, componentId: ComponentId): Boolean = records.runOn(entity) { archetype, _ -> componentId in archetype } + + fun parentsOf(entity: EntityId): EntityIdArray { + return getRelationsFor(entity, components.childOf, components.any) + .map { it.target } + .toSet() + .toULongArray() + } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ArchetypeHelpers.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ArchetypeHelpers.kt deleted file mode 100644 index fcb8d500c..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ArchetypeHelpers.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.mineinabyss.geary.helpers - -import com.mineinabyss.geary.datatypes.EntityType -import com.mineinabyss.geary.engine.archetypes.Archetype -import com.mineinabyss.geary.modules.archetypes - -fun EntityType.getArchetype(): Archetype = - archetypes.archetypeProvider.getArchetype(this) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ComponentHelpers.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ComponentHelpers.kt index 55cff9d1b..a79ffc4be 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ComponentHelpers.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ComponentHelpers.kt @@ -1,9 +1,10 @@ package com.mineinabyss.geary.helpers import com.mineinabyss.geary.datatypes.* +import com.mineinabyss.geary.modules.Geary import kotlin.reflect.KClass -fun EntityId.readableString(): String = buildString { +fun EntityId.readableString(world: Geary): String = buildString { val id = this@readableString if (id.hasRole(RELATION)) { append(id.toRelation().toString()) @@ -12,7 +13,7 @@ fun EntityId.readableString(): String = buildString { if (id.hasRole(RELATION)) append("R") else append('-') if (id.hasRole(HOLDS_DATA)) append("D") else append('-') append(" ") - val componentName = (id.getComponentInfo()?.kClass as? KClass<*>)?.simpleName + val componentName = (world.getComponentInfo(id)?.kClass as? KClass<*>)?.simpleName if (componentName == null) append(id and ENTITY_MASK) else append(componentName) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EngineHelpers.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EngineHelpers.kt index 08bc03dbe..148ec40dd 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EngineHelpers.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EngineHelpers.kt @@ -2,24 +2,24 @@ package com.mineinabyss.geary.helpers import com.mineinabyss.geary.components.ComponentInfo import com.mineinabyss.geary.datatypes.* -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.modules.Geary import kotlin.reflect.KClass import kotlin.reflect.KClassifier import kotlin.reflect.KType import kotlin.reflect.typeOf /** Creates a new empty entity. May reuse recently deleted entity ids. */ -fun entity(): Entity = geary.entityProvider.create() +fun Geary.entity(): Entity = Entity(entityProvider.create(), this) /** @see entity */ -inline fun entity(run: Entity.() -> Unit): Entity = entity().apply(run) +inline fun Geary.entity(run: Entity.() -> Unit): Entity = entity().apply(run) /** Creates a new empty entity that will get removed once [run] completes or fails. */ -inline fun temporaryEntity( - run: (Entity) -> T +inline fun Geary.temporaryEntity( + run: (Entity) -> T, ): T { val entity = entity { - add(geary.components.suppressRemoveEvent, noEvent = true) + add(components.suppressRemoveEvent, noEvent = true) } return try { run(entity) @@ -28,24 +28,24 @@ inline fun temporaryEntity( } } -inline fun component(): Entity = component(T::class) +inline fun Geary.component(): Entity = component(T::class) -fun component(kClass: KClass<*>): Entity = componentId(kClass).toGeary() +fun Geary.component(kClass: KClass<*>): Entity = componentId(kClass).toGeary() /** Gets or registers the id of a component of type [T] */ -inline fun componentId(): ComponentId = componentId(T::class) +inline fun Geary.componentId(): ComponentId = componentId(T::class) /** Gets or registers the id of a component of type [T], adding the [HOLDS_DATA] role if [T] is not nullable. */ -inline fun componentIdWithNullable(): ComponentId = +inline fun Geary.componentIdWithNullable(): ComponentId = componentId().withRole(if (typeOf().isMarkedNullable) NO_ROLE else HOLDS_DATA) /** Gets or registers the id of a component by its [kType]. */ -fun componentId(kType: KType): ComponentId = +fun Geary.componentId(kType: KType): ComponentId = componentId(kType.classifier ?: error("No classifier found for type $kType")) /** Gets or registers the id of a component by its [kClass]. */ -fun componentId(kClass: KClassifier): ComponentId = - geary.componentProvider.getOrRegisterComponentIdForClass(kClass) +fun Geary.componentId(kClass: KClassifier): ComponentId = + componentProvider.getOrRegisterComponentIdForClass(kClass) @Deprecated("Should not be getting an id for an id!", ReplaceWith("componentId(component)")) @@ -54,7 +54,6 @@ fun componentId(kClass: KClass): Nothing = error("Trying to access id for component id") /** Gets the [ComponentInfo] component from a component's id. */ -fun ComponentId.getComponentInfo(): ComponentInfo? = - this.toGeary().get() +fun Geary.getComponentInfo(component: ComponentId): ComponentInfo? = component.toGeary().get() -inline fun cId(): ComponentId = componentId() +inline fun Geary.cId(): ComponentId = componentId() diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EntityHelpers.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EntityHelpers.kt index cf4abf3ee..19c01731f 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EntityHelpers.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EntityHelpers.kt @@ -4,16 +4,6 @@ import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.ENTITY_MASK import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.datatypes.EntityId - -/** Gets the entity associated with this [EntityId], stripping it of any roles, and runs code on it. */ -inline fun EntityId.toGeary(run: Entity.() -> Unit): Entity = toGeary().apply(run) - -/** Gets the entity associated with this [EntityId], stripping it of any roles. */ -fun EntityId.toGeary(): Entity = Entity(this and ENTITY_MASK) - -/** Gets the entity associated with this [Long]. */ -fun Long.toGeary(): Entity = Entity(toULong() and ENTITY_MASK) - -val NO_ENTITY: Entity = 0L.toGeary() +import com.mineinabyss.geary.modules.Geary const val NO_COMPONENT: ComponentId = 0uL diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/Relationship.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/Relationship.kt index f5b7f140c..97a15ca9f 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/Relationship.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/Relationship.kt @@ -47,16 +47,16 @@ fun Entity.clearChildren() { /** Gets the first parent of this entity */ val Entity.parent: Entity? - get() = getRelations().firstOrNull()?.target?.toGeary() + get() = with(world) { getRelations().firstOrNull()?.target?.toGeary() } /** Runs code on the first parent of this entity. */ inline fun Entity.onParent( parent: Entity? = this.parent, - run: Entity.() -> Unit + run: Entity.() -> Unit, ) { parent ?: return run(parent) } val Entity.parents: Set - get() = getRelations().mapTo(mutableSetOf()) { it.target.toGeary() } + get() = with(world) { getRelations().mapTo(mutableSetOf()) { it.target.toGeary() } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/WithOperator.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/WithOperator.kt index 0cb5e1e2a..a6a2b59ff 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/WithOperator.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/WithOperator.kt @@ -6,7 +6,7 @@ import com.mineinabyss.geary.datatypes.Entity as GE /** Gets a component of type [T] from an entity, returning null if [T] is nullable, or an error otherwise. */ inline fun GE.nullOrError(): T { - val data = get(componentId()) as? T? + val data = get(world.componentId()) as? T? if (!typeOf().isMarkedNullable && data == null) { error("") } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineInitializer.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineInitializer.kt new file mode 100644 index 000000000..7480af979 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineInitializer.kt @@ -0,0 +1,19 @@ +package com.mineinabyss.geary.modules + +import com.mineinabyss.geary.engine.Pipeline +import com.mineinabyss.geary.engine.archetypes.ArchetypeEngine +import com.mineinabyss.geary.engine.archetypes.ComponentAsEntityProvider + +class ArchetypeEngineInitializer( + val beginTickingOnStart: Boolean, + private val pipeline: Pipeline, + private val engine: ArchetypeEngine, +) : EngineInitializer { + override fun init() { + } + + override fun start() { + pipeline.runStartupTasks() + if (beginTickingOnStart) engine.start() + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt index a2941d647..211d6066c 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt @@ -1,62 +1,94 @@ package com.mineinabyss.geary.modules import co.touchlab.kermit.Logger -import com.mineinabyss.geary.addons.GearyPhase import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap -import com.mineinabyss.geary.engine.Components -import com.mineinabyss.geary.engine.PipelineImpl +import com.mineinabyss.geary.datatypes.maps.SynchronizedArrayTypeMap +import com.mineinabyss.geary.datatypes.maps.TypeMap +import com.mineinabyss.geary.engine.* import com.mineinabyss.geary.engine.archetypes.* import com.mineinabyss.geary.engine.archetypes.operations.ArchetypeMutateOperations import com.mineinabyss.geary.engine.archetypes.operations.ArchetypeReadOperations import com.mineinabyss.geary.observers.ArchetypeEventRunner -import com.mineinabyss.idofront.di.DI +import com.mineinabyss.geary.observers.EventRunner +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.plus +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.core.module.dsl.withOptions +import org.koin.dsl.module +import kotlin.coroutines.CoroutineContext import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds -val archetypes: ArchetypeEngineModule by DI.observe() - -open class ArchetypeEngineModule( - tickDuration: Duration = 50.milliseconds, -) : GearyModule { - override val logger = Logger.withTag("Geary") - override val queryManager = ArchetypeQueryManager() - - override val engine = ArchetypeEngine(tickDuration) - override val eventRunner = ArchetypeEventRunner() - override val pipeline = PipelineImpl() - - open val records: ArrayTypeMap = ArrayTypeMap() - - override val read = ArchetypeReadOperations() - override val write = ArchetypeMutateOperations() - override val entityProvider = EntityByArchetypeProvider() - override val componentProvider = ComponentAsEntityProvider() - override val defaults: Defaults = Defaults() +internal object ArchetypesModules { + // Module for any classes without other dependencies as parameters + val noDependencies get() = module { + single { Geary } + single { if (getProperty("useSynchronized")) SynchronizedArrayTypeMap() else ArrayTypeMap() } withOptions { + bind() + } + } - open val archetypeProvider: ArchetypeProvider = SimpleArchetypeProvider() + val archetypes get() = module { + includes(noDependencies) + single { ArchetypeQueryManager() } withOptions { bind() } + singleOf(::SimpleArchetypeProvider) { bind() } + } - override val components by lazy { Components() } + val entities get() = module { + includes(archetypes) + single { + EntityByArchetypeProvider(getProperty("reuseIDsAfterRemoval"), get(), get()) + } withOptions { bind() } + } - companion object : GearyModuleProviderWithDefault { - override fun default(): ArchetypeEngineModule { - return ArchetypeEngineModule() - } + val components get() = module { + includes(entities) + singleOf(::ComponentAsEntityProvider) { bind() } + singleOf(::Components) + } - override fun init(module: ArchetypeEngineModule) { - DI.add(module) - DI.add(module) - module.entityProvider.init(module.records, module.archetypeProvider.rootArchetype) - module.write.init(module.records) - module.componentProvider.createComponentInfo() - } + val core get() = module { + includes(components) + singleOf(::ArchetypeReadOperations) { bind() } + singleOf(::PipelineImpl) { bind() } + } - override fun start(module: ArchetypeEngineModule) { - module { - on(GearyPhase.ENABLE) { - module.engine.start() - } - } - geary.pipeline.runStartupTasks() - } + val engine get() = module { + includes(core) + single { + ArchetypeEngine(get(), get(), getProperty("tickDuration"), getProperty("engineThread")) + } withOptions { bind() } } } + +fun ArchetypeEngineModule( + tickDuration: Duration = 50.milliseconds, + reuseIDsAfterRemoval: Boolean = true, + useSynchronized: Boolean = false, + beginTickingOnStart: Boolean = true, + defaults: Defaults = Defaults(), + engineThread: CoroutineContext = (CoroutineScope(Dispatchers.Default) + CoroutineName("Geary Engine")).coroutineContext, +) = GearyModule( + module { + includes(ArchetypesModules.engine) + singleOf(::ArchetypeEventRunner) { bind() } + singleOf(::ArchetypeMutateOperations) { bind() } + singleOf(::EntityRemove) + single { + ArchetypeEngineInitializer(getProperty("beginTickingOnStart"), get(), get()) + } withOptions { + bind() + } + singleOf(::MutableAddons) + }, properties = mapOf( + "tickDuration" to tickDuration, + "reuseIDsAfterRemoval" to reuseIDsAfterRemoval, + "useSynchronized" to useSynchronized, + "beginTickingOnStart" to beginTickingOnStart, + "defaults" to defaults, + "engineThread" to engineThread + ) +) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/EngineInitializer.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/EngineInitializer.kt new file mode 100644 index 000000000..26fd2cf48 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/EngineInitializer.kt @@ -0,0 +1,6 @@ +package com.mineinabyss.geary.modules + +interface EngineInitializer { + fun init() + fun start() +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/Geary.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/Geary.kt new file mode 100644 index 000000000..6bd507636 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/Geary.kt @@ -0,0 +1,174 @@ +package com.mineinabyss.geary.modules + +import co.touchlab.kermit.Logger +import co.touchlab.kermit.mutableLoggerConfigInit +import co.touchlab.kermit.platformLogWriter +import com.mineinabyss.geary.addons.dsl.Addon +import com.mineinabyss.geary.datatypes.* +import com.mineinabyss.geary.datatypes.family.Family +import com.mineinabyss.geary.datatypes.family.MutableFamily +import com.mineinabyss.geary.datatypes.family.family +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap +import com.mineinabyss.geary.engine.* +import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider +import com.mineinabyss.geary.engine.archetypes.EntityRemove +import com.mineinabyss.geary.helpers.componentId +import com.mineinabyss.geary.helpers.componentIdWithNullable +import com.mineinabyss.geary.observers.EventRunner +import com.mineinabyss.geary.observers.builders.ObserverWithData +import com.mineinabyss.geary.observers.builders.ObserverWithoutData +import com.mineinabyss.geary.systems.builders.SystemBuilder +import com.mineinabyss.geary.systems.query.CachedQuery +import com.mineinabyss.geary.systems.query.Query +import org.koin.core.Koin +import org.koin.core.KoinApplication +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import kotlin.reflect.KClass + +/** + * Root class for users to access geary functionality. + * + * Anything exposed to the user should be accessible here without + * having to call functions on classes in the module, + * it simply acts as a container for all dependencies. + * + * Any functions that modify the state of the engine modify it right away, + * they are not scheduled for load phases like [GearySetup] is. + */ +interface Geary : KoinComponent { + abstract val application: KoinApplication + open val logger: Logger get() = application.koin.get() + override fun getKoin(): Koin = application.koin + + // By default, we always get the latest instance of deps, the Impl class gets them once for user + // access where the engine isn't expected to be reloaded (ex. like it might be in tests) + val eventRunner: EventRunner get() = get() + val read: EntityReadOperations get() = get() + val write: EntityMutateOperations get() = get() + val queryManager: QueryManager get() = get() + val pipeline: Pipeline get() = get() + val entityProvider: EntityProvider get() = get() + val entityRemoveProvider: EntityRemove get() = get() + val components: Components get() = get() + val componentProvider: ComponentProvider get() = get() + val records: ArrayTypeMap get() = get() + val engine: GearyEngine get() = get() + val addons: MutableAddons get() = get() + + fun , Inst> getAddon(addon: T): Inst = + addons.getInstance(addon) ?: error("Instance for addon ${addon.name} not found") + + fun , Inst> getAddonOrNull(addon: T?): Inst? = addon?.let { addons.getInstance(addon) } + + fun , Conf> getConfiguration(addon: T): Conf = addons.getConfig(addon) + + // Queries + + fun findEntities(family: Family): EntityArray { + return queryManager.getEntitiesMatching(family).toEntityArray(world = this) + } + + fun cache( + query: T, + ): CachedQuery { + return queryManager.trackQuery(query) + } + + fun cache( + create: (Geary) -> T, + ): CachedQuery { + return cache(create(this)) + } + + fun system( + query: T, + ): SystemBuilder { + val defaultName = Throwable().stackTraceToString() + .lineSequence() + .drop(2) // First line error, second line is this function + .first() + .trim() + .substringBeforeLast("(") + .substringAfter("$") + .substringAfter("Kt.") + .substringAfter("create") + + return SystemBuilder(defaultName, query, pipeline) + } + + fun loadAddon(addon: Addon<*, *>) { + addons.init(addons.getOrPut(this, addon), setup = GearySetup(application)) + } + + + fun relationOf(kind: KClass<*>, target: KClass<*>): Relation = + Relation.of(componentId(kind), componentId(target)) + + fun EntityType.getArchetype(): Archetype = + get().getArchetype(this) + + /** Gets the entity associated with this [EntityId], stripping it of any roles. */ + fun EntityId.toGeary(): Entity = Entity(this and ENTITY_MASK, this@Geary) + + /** Gets the entity associated with this [Long]. */ + fun Long.toGeary(): Entity = Entity(toULong() and ENTITY_MASK, this@Geary) + + val NO_ENTITY: Entity get() = 0L.toGeary() + + companion object : Logger(mutableLoggerConfigInit(listOf(platformLogWriter())), "Geary") { + operator fun invoke(application: KoinApplication, logger: Logger? = null): Geary = Impl(application, logger) + } + + class Impl( + override val application: KoinApplication, + logger: Logger? = null, + ) : Geary { + override val logger: Logger = logger ?: super.logger + override val eventRunner: EventRunner by inject() + override val read: EntityReadOperations by inject() + override val write: EntityMutateOperations by inject() + override val queryManager: QueryManager by inject() + override val pipeline: Pipeline by inject() + override val entityProvider: EntityProvider by inject() + override val entityRemoveProvider: EntityRemove by inject() + override val components: Components by inject() + override val componentProvider: ComponentProvider by inject() + override val records: ArrayTypeMap by inject() + override val engine: GearyEngine by inject() + } + + fun stringify() = application.toString().removePrefix("org.koin.core.KoinApplication") +} + +inline fun Geary.relationOf(): Relation = + Relation.of(componentIdWithNullable(), componentId()) + +inline fun Geary.relationOf(target: Entity): Relation = + Relation.of(componentIdWithNullable(), target.id) + +inline fun Geary.get() = application.koin.get() + +// TODO simple api for running queries in place without caching +inline fun execute( + query: T, + run: T.() -> Unit = {}, +): CachedQuery { + TODO() +} + +inline fun Geary.observe(): ObserverWithoutData { + return ObserverWithoutData(listOf(componentId()), world = this) { + eventRunner.addObserver(it) + } +} + +inline fun Geary.observeWithData(): ObserverWithData { + return ObserverWithData(listOf(componentId()), world = this) { + eventRunner.addObserver(it) + } +} + +inline fun Geary.findEntities(init: MutableFamily.Selector.And.() -> Unit) = + findEntities(family(init)) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyConfiguration.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyConfiguration.kt deleted file mode 100644 index b7001c604..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyConfiguration.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.mineinabyss.geary.modules - -import com.mineinabyss.geary.addons.GearyPhase -import com.mineinabyss.geary.addons.Namespaced -import com.mineinabyss.geary.addons.dsl.GearyAddon -import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault -import com.mineinabyss.geary.addons.dsl.GearyDSL -import com.mineinabyss.idofront.di.DI -import kotlin.reflect.KClass - -@GearyDSL -class GearyConfiguration( - val module: GearyModule -) { - val installedAddons = mutableMapOf>, Any>() - inline fun , reified Module : Any> install( - addon: T, - ): Module { - val module = DI.getOrNull() - if (module != null) return module - return install(addon, addon.default()) - } - - inline fun , reified Module : Any> install( - addon: T, - module: Module, - ): Module = with(addon) { - DI.add(module) - module.install() - }.let { module } - - fun namespace(namespace: String, configure: Namespaced.() -> Unit) { - Namespaced(namespace, this).configure() - } - - /** Runs a block during [GearyPhase.INIT_COMPONENTS] */ - fun components(configure: GearyModule.() -> Unit) { - on(GearyPhase.INIT_COMPONENTS) { - module.configure() - } - } - - /** Runs a block during [GearyPhase.INIT_SYSTEMS] */ - fun systems(configure: GearyModule.() -> Unit) { - on(GearyPhase.INIT_SYSTEMS) { - module.configure() - } - } - - /** Runs a block during [GearyPhase.INIT_ENTITIES] */ - fun entities(configure: GearyModule.() -> Unit) { - on(GearyPhase.INIT_ENTITIES) { - module.configure() - } - } - - /** - * Allows defining actions that should run at a specific phase during startup - * - * Within its context, invoke a [GearyPhase] to run something during it, ex: - * - * ``` - * GearyLoadPhase.ENABLE { - * // run code here - * } - * ``` - */ - fun on(phase: GearyPhase, run: () -> Unit) { - geary.pipeline.runOnOrAfter(phase, run) - } -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModule.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModule.kt index 042b772f9..0a989b90c 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModule.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModule.kt @@ -1,53 +1,39 @@ package com.mineinabyss.geary.modules -import co.touchlab.kermit.Logger -import com.mineinabyss.geary.addons.dsl.GearyDSL -import com.mineinabyss.geary.engine.* -import com.mineinabyss.geary.observers.EventRunner -import com.mineinabyss.idofront.di.DI - -val geary: GearyModule by DI.observe() - -fun geary( - moduleProvider: GearyModuleProviderWithDefault, - configuration: GearyConfiguration.() -> Unit = {} -) { - val module = moduleProvider.default() - geary(moduleProvider, module, configuration) +import org.koin.core.module.Module +import org.koin.dsl.koinApplication +import org.koin.dsl.module + +fun geary( + module: GearyModule, + configure: GearySetup.() -> Unit = {}, +): UninitializedGearyModule { + val application = koinApplication { + properties(module.properties) + modules(module.module) + } + val initializer = application.koin.get() + initializer.init() + val setup = GearySetup(application) + configure(setup) + return UninitializedGearyModule(setup, initializer) } -fun geary( - moduleProvider: GearyModuleProvider, - module: T, - configuration: GearyConfiguration.() -> Unit = {} +data class UninitializedGearyModule( + val setup: GearySetup, + val initializer: EngineInitializer, ) { - moduleProvider.init(module) - module.invoke(configuration) - moduleProvider.start(module) -} - -@GearyDSL -interface GearyModule { - val logger: Logger - val entityProvider: EntityProvider - val componentProvider: ComponentProvider + inline fun configure(configure: GearySetup.() -> Unit) = setup.configure() - val read: EntityReadOperations - val write: EntityMutateOperations - - val queryManager: QueryManager - val components: Components - val engine: Engine - - val eventRunner: EventRunner - val pipeline: Pipeline - - val defaults: Defaults - - operator fun invoke(configure: GearyConfiguration.() -> Unit) { - GearyConfiguration(this).apply(configure) + fun start(): Geary { + val world = Geary(setup.application) + world.addons.initAll(setup) + initializer.start() + return world } - - companion object } +data class GearyModule( + val module: Module, + val properties: Map = emptyMap(), +) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModuleProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModuleProvider.kt deleted file mode 100644 index 323240e8c..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModuleProvider.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.mineinabyss.geary.modules - -interface GearyModuleProvider { - fun init(module: T) - fun start(module: T) -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModuleProviderWithDefault.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModuleProviderWithDefault.kt deleted file mode 100644 index a093191f7..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModuleProviderWithDefault.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.mineinabyss.geary.modules - -interface GearyModuleProviderWithDefault : GearyModuleProvider { - fun default(): T -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearySetup.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearySetup.kt new file mode 100644 index 000000000..9391ea404 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearySetup.kt @@ -0,0 +1,34 @@ +package com.mineinabyss.geary.modules + +import co.touchlab.kermit.Logger +import com.mineinabyss.geary.addons.Namespaced +import com.mineinabyss.geary.addons.dsl.Addon +import com.mineinabyss.geary.addons.dsl.AddonSetup +import com.mineinabyss.geary.addons.dsl.createAddon +import org.koin.core.KoinApplication + +/** + * Represents a Geary engine whose dependencies have been created in a [GearyModule] and is ready to have addons + * installed. Load phases are accessible here and will be called once start gets called. + */ +class GearySetup( + val application: KoinApplication, +) { + val logger = application.koin.get() + val geary = Geary(application) + + inline fun , Conf> install(addon: T, configure: Conf.() -> Unit = {}): T { + geary.addons.getOrPut(geary, addon).apply { config.configure() } + return addon + } + + inline fun install(name: String, crossinline init: AddonSetup.() -> Unit) { + install(createAddon(name) { + init() + }) + } + + fun namespace(namespace: String, configure: Namespaced.() -> Unit) { + Namespaced(namespace, this).configure() + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/MutableAddons.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/MutableAddons.kt new file mode 100644 index 000000000..5c38dedfa --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/MutableAddons.kt @@ -0,0 +1,38 @@ +package com.mineinabyss.geary.modules + +import com.mineinabyss.geary.addons.dsl.Addon + +class MutableAddons { + data class AddonToConfig(val addon: Addon, val config: T) + + @PublishedApi + internal val addons = mutableMapOf>() + internal val instances = mutableMapOf() + internal val addonsOrder = mutableListOf>() + + fun getInstance(addon: Addon<*, I>): I? { + return instances[addon.name] as? I + } + + fun getOrPut(world: Geary, addon: Addon, customConfiguration: (() -> T)? = null): AddonToConfig { + return addons.getOrPut(addon.name) { + AddonToConfig(addon, customConfiguration?.invoke() ?: addon.defaultConfiguration(world)) + .also { addonsOrder.add(it) } + } as AddonToConfig + } + + fun getConfig(addon: Addon): T { + return addons[addon.name]?.config as? T ?: error("Config for addon ${addon.name} not found") + } + + fun init(addon: AddonToConfig<*>, setup: GearySetup) { + val geary = Geary(setup.application, setup.logger.withTag(addon.addon.name)) + instances[addon.addon.name] = (addon.addon.onInstall as Geary.(Any?) -> Any).invoke(geary, addon.config) + } + + fun initAll(setup: GearySetup) { + addonsOrder.forEach { addon -> + init(addon, setup) + } + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt index bc05055a8..03a74c7dc 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt @@ -1,30 +1,12 @@ package com.mineinabyss.geary.modules -import com.mineinabyss.geary.datatypes.maps.SynchronizedArrayTypeMap -import com.mineinabyss.geary.engine.archetypes.EntityByArchetypeProvider - /** * An engine module that initializes the engine but does not start it, useful for testing. * * No pipeline tasks are run, and the engine won't be scheduled for ticking. * Engine ticks may still be called manually. */ -class TestEngineModule( - reuseIDsAfterRemoval: Boolean = true, - useSynchronized: Boolean = false, -) : ArchetypeEngineModule() { - override val records = if (useSynchronized) SynchronizedArrayTypeMap() else super.records - override val entityProvider = EntityByArchetypeProvider(reuseIDsAfterRemoval) - - companion object : GearyModuleProviderWithDefault { - override fun init(module: TestEngineModule) { - ArchetypeEngineModule.init(module) - } - - override fun start(module: TestEngineModule) = Unit - - override fun default(): TestEngineModule { - return TestEngineModule() - } - } -} +// TODO set beginTickingOnStart = false property +val TestEngineModule get() = ArchetypeEngineModule( + beginTickingOnStart = false +) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ArchetypeEventRunner.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ArchetypeEventRunner.kt index b17ce19dd..64efb8e26 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ArchetypeEventRunner.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ArchetypeEventRunner.kt @@ -2,15 +2,22 @@ package com.mineinabyss.geary.observers import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.datatypes.* +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap +import com.mineinabyss.geary.engine.ComponentProvider import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.engine.archetypes.operations.ArchetypeReadOperations +import com.mineinabyss.geary.engine.id import com.mineinabyss.geary.helpers.contains import com.mineinabyss.geary.helpers.fastForEach -import com.mineinabyss.geary.helpers.toGeary -import com.mineinabyss.geary.modules.archetypes -import com.mineinabyss.geary.modules.geary -class ArchetypeEventRunner : EventRunner { - private val eventToObserversMap = EventToObserversMap() +class ArchetypeEventRunner( + val reader: ArchetypeReadOperations, + val compProvider: ComponentProvider, + val records: ArrayTypeMap +) : EventRunner { + val observerComponent: ComponentId = compProvider.id() + + private val eventToObserversMap = EventToObserversMap(records) override fun addObserver(observer: Observer) { eventToObserversMap.addObserver(observer) } @@ -18,16 +25,15 @@ class ArchetypeEventRunner : EventRunner { private inline fun matchObservers( eventType: ComponentId, involvedComponent: ComponentId, - entity: Entity, + entity: EntityId, exec: (Observer, Archetype, row: Int) -> Unit ) { - val observerComp = geary.components.observer - val records = archetypes.records val involved = involvedComponent.withoutRole(HOLDS_DATA) // Run entity observers - records.runOn(entity) { archetype, _ -> archetype.getRelationsByKind(observerComp) }.forEach { relation -> - val observerList = Relation.of(relation).target.toGeary().get() ?: return@forEach + + records.runOn(entity) { archetype, _ -> archetype.getRelationsByKind(observerComponent) }.forEach { relation -> + val observerList = reader.get(Relation.of(relation).target, compProvider.id()) as? EventToObserversMap ?: return@forEach observerList[eventType]?.forEach(involved, entity, exec) } @@ -39,7 +45,7 @@ class ArchetypeEventRunner : EventRunner { eventType: ComponentId, eventData: Any?, involvedComponent: ComponentId, - entity: Entity, + entity: EntityId, ) { matchObservers(eventType, involvedComponent, entity) { observer, archetype, row -> // Observer may change the entity record, so we must get each time. diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventRunner.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventRunner.kt index e620ea7f2..904d774f2 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventRunner.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventRunner.kt @@ -2,6 +2,7 @@ package com.mineinabyss.geary.observers import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId interface EventRunner { fun addObserver(observer: Observer) @@ -10,6 +11,6 @@ interface EventRunner { eventType: ComponentId, eventData: Any?, involvedComponent: ComponentId, - entity: Entity, + entity: EntityId, ) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventToObserversMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventToObserversMap.kt index 0f6d44592..3060603ec 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventToObserversMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/EventToObserversMap.kt @@ -3,13 +3,16 @@ package com.mineinabyss.geary.observers import androidx.collection.LongSparseArray import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.getOrPut +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap -class EventToObserversMap { +class EventToObserversMap( + val records: ArrayTypeMap, +) { private val eventToObserverMap = LongSparseArray() fun addObserver(observer: Observer) { observer.listenToEvents.forEach { event -> - eventToObserverMap.getOrPut(event.toLong()) { ObserverList() }.add(observer) + eventToObserverMap.getOrPut(event.toLong()) { ObserverList(records) }.add(observer) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/Observer.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/Observer.kt index add0c7bc4..f615e1122 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/Observer.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/Observer.kt @@ -2,6 +2,7 @@ package com.mineinabyss.geary.observers import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.systems.query.Query @@ -16,5 +17,5 @@ data class Observer( ) fun interface ObserverHandle { - fun run(entity: Entity, data: Any?, involvedComponent: ComponentId?) + fun run(entity: EntityId, data: Any?, involvedComponent: ComponentId?) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ObserverList.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ObserverList.kt index f8ae57361..3a9209973 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ObserverList.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/ObserverList.kt @@ -4,13 +4,15 @@ import androidx.collection.LongSparseArray import androidx.collection.MutableObjectList import androidx.collection.mutableObjectListOf import com.mineinabyss.geary.datatypes.ComponentId -import com.mineinabyss.geary.datatypes.GearyEntity +import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.getOrPut +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap import com.mineinabyss.geary.engine.archetypes.Archetype import com.mineinabyss.geary.helpers.NO_COMPONENT -import com.mineinabyss.geary.modules.archetypes -class ObserverList { +class ObserverList( + val records: ArrayTypeMap, +) { val involved2Observer = LongSparseArray>() fun add(observer: Observer) { @@ -21,9 +23,7 @@ class ObserverList { } } - inline fun forEach(involvedComp: ComponentId, entity: GearyEntity, exec: (Observer, Archetype, row: Int) -> Unit) { - val records = archetypes.records - + inline fun forEach(involvedComp: ComponentId, entity: EntityId, exec: (Observer, Archetype, row: Int) -> Unit) { involved2Observer[0L]?.forEach { records.runOn(entity) { archetype, row -> exec(it, archetype, row) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/builders/ObserverBuilder.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/builders/ObserverBuilder.kt index a05cd7978..e29906497 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/builders/ObserverBuilder.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/builders/ObserverBuilder.kt @@ -3,6 +3,7 @@ package com.mineinabyss.geary.observers.builders import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.datatypes.GearyEntityType import com.mineinabyss.geary.datatypes.family.family +import com.mineinabyss.geary.engine.ComponentProvider import com.mineinabyss.geary.observers.Observer import com.mineinabyss.geary.systems.query.Query import com.mineinabyss.geary.systems.query.ShorthandQuery @@ -40,6 +41,7 @@ data class QueryInvolvingObserverBuilder( } data class ObserverBuilder( + val comp: ComponentProvider, val events: ObserverEventsBuilder, val involvedComponents: EntityType, val matchQueries: List = emptyList(), diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/builders/ObserverEventsBuilder.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/builders/ObserverEventsBuilder.kt index d1723bee0..34d99a29f 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/builders/ObserverEventsBuilder.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/builders/ObserverEventsBuilder.kt @@ -1,10 +1,9 @@ package com.mineinabyss.geary.observers.builders import com.mineinabyss.geary.datatypes.* -import com.mineinabyss.geary.helpers.NO_ENTITY -import com.mineinabyss.geary.helpers.cId -import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.engine.ComponentProvider +import com.mineinabyss.geary.engine.id +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.observers.Observer import com.mineinabyss.geary.systems.query.Query import com.mineinabyss.geary.systems.query.QueryShorthands @@ -12,25 +11,25 @@ import com.mineinabyss.geary.systems.query.ShorthandQuery data class ObserverWithoutData( override val listenToEvents: List, - override val module: GearyModule, + override val world: Geary, override val onBuild: (Observer) -> Unit, ) : ObserverEventsBuilder() { override val mustHoldData: Boolean = false - inline fun or() = copy(listenToEvents = listenToEvents + componentId()) + inline fun or() = copy(listenToEvents = listenToEvents + comp.id()) private val context = object : ObserverContext { - override var entity: Entity = NO_ENTITY + override var entity: Entity = world.NO_ENTITY } - override fun provideContext(entity: GearyEntity, data: Any?): ObserverContext { - context.entity = entity + override fun provideContext(entity: EntityId, data: Any?): ObserverContext { + context.entity = GearyEntity(entity, world) return context } } data class ObserverWithData( override val listenToEvents: List, - override val module: GearyModule, + override val world: Geary, override val onBuild: (Observer) -> Unit, ) : ObserverEventsBuilder>() { override val mustHoldData: Boolean = true @@ -38,50 +37,60 @@ data class ObserverWithData( private val context = object : ObserverContextWithData { var data: R? = null override val event: R get() = data!! - override var entity: Entity = NO_ENTITY + override var entity: Entity = world.NO_ENTITY } - override fun provideContext(entity: GearyEntity, data: Any?): ObserverContextWithData { - context.entity = entity + override fun provideContext(entity: EntityId, data: Any?): ObserverContextWithData { + context.entity = GearyEntity(entity, world) context.data = data as R return context } } abstract class ObserverEventsBuilder : ExecutableObserver { + abstract val world: Geary abstract val listenToEvents: List - abstract val module: GearyModule abstract val mustHoldData: Boolean abstract val onBuild: (Observer) -> Unit - abstract fun provideContext(entity: GearyEntity, data: Any?): Context + val comp: ComponentProvider get() = world.componentProvider + + abstract fun provideContext(entity: EntityId, data: Any?): Context fun involving(components: EntityType): ObserverBuilder { - return ObserverBuilder(this, components) + return ObserverBuilder(comp, this, components) } inline fun involving(size1: QueryShorthands.Size1? = null): ObserverBuilder { - return ObserverBuilder(this, entityTypeOf(componentId())) + return ObserverBuilder(comp, this, entityTypeOf(comp.id())) } inline fun involving(size2: QueryShorthands.Size2? = null): ObserverBuilder { - return ObserverBuilder(this, entityTypeOf(cId(), cId())) + return ObserverBuilder(comp, this, entityTypeOf(comp.id(), comp.id())) } inline fun involving(size3: QueryShorthands.Size3? = null): ObserverBuilder { - return ObserverBuilder(this, entityTypeOf(cId(), cId(), cId())) + return ObserverBuilder(comp, this, entityTypeOf(comp.id(), comp.id(), comp.id())) } inline fun involving(size4: QueryShorthands.Size4? = null): ObserverBuilder { - return ObserverBuilder(this, entityTypeOf(cId(), cId(), cId(), cId())) + return ObserverBuilder( + comp, + this, + entityTypeOf(comp.id(), comp.id(), comp.id(), comp.id()) + ) } inline fun involving(size5: QueryShorthands.Size5? = null): ObserverBuilder { - return ObserverBuilder(this, entityTypeOf(cId(), cId(), cId(), cId(), cId())) + return ObserverBuilder( + comp, + this, + entityTypeOf(comp.id(), comp.id(), comp.id(), comp.id(), comp.id()) + ) } fun involvingAny(): ObserverBuilder { - return ObserverBuilder(this, entityTypeOf()) + return ObserverBuilder(comp, this, entityTypeOf()) } override fun filter(vararg queries: Query) = involvingAny().filter(*queries) @@ -91,6 +100,6 @@ abstract class ObserverEventsBuilder : ExecutableObserver { fun involving(involvingQuery: Q) = QueryInvolvingObserverBuilder( involvingQuery, - ObserverBuilder(this, involvingQuery.involves, listOf(involvingQuery)) + ObserverBuilder(comp, this, involvingQuery.involves, listOf(involvingQuery)) ) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/entity/EntityObserver.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/entity/EntityObserver.kt index 6d0f139ad..fb991de9b 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/entity/EntityObserver.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/entity/EntityObserver.kt @@ -5,22 +5,23 @@ import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.modules.ArchetypeEngineModule import com.mineinabyss.geary.observers.EventToObserversMap import com.mineinabyss.geary.observers.Observer import com.mineinabyss.geary.observers.builders.* inline fun GearyEntity.observe(): ObserverEventsBuilder { - return observe(componentId()) + return observe(world.componentId()) } inline fun GearyEntity.observeWithData(): ObserverEventsBuilder> { - return observeWithData(componentId()) + return observeWithData(world.componentId()) } fun GearyEntity.attachObserver(observer: Observer) { - val observerEntity = entity { - set(EventToObserversMap().apply { addObserver(observer) }) + val observerEntity = world.entity { + // TODO avoid cast + set(EventToObserversMap(world.records).apply { addObserver(observer) }) addRelation(this@attachObserver) // Remove entity when original is removed } //TODO remove when prefabs auto propagate component adds down @@ -29,10 +30,10 @@ fun GearyEntity.attachObserver(observer: Observer) { } fun GearyEntity.observe(vararg events: ComponentId): ObserverEventsBuilder { - return ObserverWithoutData(events.toList(), geary, ::attachObserver) + return ObserverWithoutData(events.toList(), world, ::attachObserver) } fun GearyEntity.observeWithData(vararg events: ComponentId): ObserverEventsBuilder> { - return ObserverWithData(events.toList(), geary, ::attachObserver) + return ObserverWithData(events.toList(), world, ::attachObserver) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/events/EntityEvents.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/events/EntityEvents.kt index 37837d8a2..ad1dfe00c 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/events/EntityEvents.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/events/EntityEvents.kt @@ -1,7 +1,7 @@ package com.mineinabyss.geary.observers.events -import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId class OnEntityRemoved -class OnExtend(val baseEntity: Entity) +class OnExtend(val baseEntity: EntityId) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/queries/CacheQueryAsMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/queries/CacheQueryAsMap.kt index 52f858661..ce3d2a8f1 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/queries/CacheQueryAsMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/observers/queries/CacheQueryAsMap.kt @@ -1,32 +1,32 @@ package com.mineinabyss.geary.observers.queries import com.mineinabyss.geary.datatypes.Entity -import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.builders.ObserverContext import com.mineinabyss.geary.observers.events.OnFirstSet import com.mineinabyss.geary.observers.events.OnRemove -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.ShorthandQuery -fun GearyModule.cacheGroupedBy( +fun Geary.cacheGroupedBy( query: Q, - groupBy: ObserverContext.(Q) -> T + groupBy: ObserverContext.(Q) -> T, ): QueryGroupedBy { return object : QueryGroupedBy(query, this) { override fun ObserverContext.groupBy(query: Q): T = groupBy(query) } } -fun GearyModule.cacheAssociatedBy( +fun Geary.cacheAssociatedBy( query: Q, - associateBy: ObserverContext.(Q) -> T + associateBy: ObserverContext.(Q) -> T, ): QueryAssociatedBy { return object : QueryAssociatedBy(query, this) { override fun ObserverContext.associateBy(query: Q): T = associateBy(query) } } -abstract class QueryGroupedBy(private val query: Q, geary: GearyModule) { +abstract class QueryGroupedBy(private val query: Q, geary: Geary) { private val map = mutableMapOf>() abstract fun ObserverContext.groupBy(query: Q): T @@ -51,7 +51,7 @@ abstract class QueryGroupedBy(private val query: Q, geary } } -abstract class QueryAssociatedBy(private val query: Q, geary: GearyModule) { +abstract class QueryAssociatedBy(private val query: Q, geary: Geary) { private val map = mutableMapOf() abstract fun ObserverContext.associateBy(query: Q): T diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorOperations.kt index 770fb0359..c3954b0a1 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorOperations.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorOperations.kt @@ -2,16 +2,19 @@ package com.mineinabyss.geary.systems.accessors import com.mineinabyss.geary.datatypes.Component import com.mineinabyss.geary.datatypes.HOLDS_DATA -import com.mineinabyss.geary.datatypes.Relation import com.mineinabyss.geary.datatypes.family.MutableFamily +import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.datatypes.withRole import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.componentIdWithNullable +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.relationOf import com.mineinabyss.geary.systems.accessors.type.* import com.mineinabyss.geary.systems.query.QueriedEntity import kotlin.reflect.typeOf abstract class AccessorOperations { + abstract val world: Geary abstract val cacheAccessors: Boolean @PublishedApi @@ -23,16 +26,16 @@ abstract class AccessorOperations { /** Accesses a component, ensuring it is on the entity. */ protected inline fun QueriedEntity.get(): ComponentAccessor { return addAccessor { - ComponentAccessor(null, componentId().withRole(HOLDS_DATA)) + ComponentAccessor(world.componentProvider, null, world.componentId().withRole(HOLDS_DATA)) } } protected inline fun QueriedEntity.getPotentiallyNullable(): ReadOnlyAccessor { val t = typeOf() return addAccessor { - val id = componentId(t).withRole(HOLDS_DATA) - val compAccessor = ComponentAccessor(null, id) - if(t.isMarkedNullable) + val id = world.componentId(t).withRole(HOLDS_DATA) + val compAccessor = ComponentAccessor(world.componentProvider, null, id) + if (t.isMarkedNullable) ComponentOrDefaultAccessor(compAccessor, id) { null } else compAccessor } as ReadOnlyAccessor @@ -40,7 +43,7 @@ abstract class AccessorOperations { /** Accesses a data stored in a relation with kind [K] and target type [T], ensuring it is on the entity. */ protected inline fun QueriedEntity.getRelation(): ComponentAccessor { - return addAccessor { ComponentAccessor(null, Relation.of().id) } + return addAccessor { ComponentAccessor(world.componentProvider, null, world.relationOf().id) } } inline fun addAccessor(create: () -> T): T { @@ -89,22 +92,23 @@ abstract class AccessorOperations { * - Note: nullability rules are still upheld with [Any]. */ protected inline fun QueriedEntity.getRelations(): RelationsAccessor { - return addAccessor { RelationsAccessor(null, componentIdWithNullable(), componentIdWithNullable()) } + return addAccessor { RelationsAccessor(world.componentProvider, null, world.componentIdWithNullable(), world.componentIdWithNullable()) } } /** @see getRelations */ protected inline fun QueriedEntity.getRelationsWithData(): RelationsWithDataAccessor { return addAccessor { RelationsWithDataAccessor( + world.componentProvider, null, - componentIdWithNullable(), - componentIdWithNullable() + world.componentIdWithNullable(), + world.componentIdWithNullable() ) } } protected operator fun QueriedEntity.invoke(init: MutableFamily.Selector.And.() -> Unit) { - val family = com.mineinabyss.geary.datatypes.family.family(init) + val family = family(init) extraFamilies.add(family) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/RelationWithData.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/RelationWithData.kt index 1a1b52ae2..4c3f9caba 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/RelationWithData.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/RelationWithData.kt @@ -1,9 +1,8 @@ package com.mineinabyss.geary.systems.accessors import com.mineinabyss.geary.datatypes.Component -import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.Relation -import com.mineinabyss.geary.helpers.toGeary /** * Helper class for getting a compact overview of data stored in a relation. @@ -13,6 +12,6 @@ data class RelationWithData( val targetData: T, val relation: Relation, ) { - val kind: Entity = relation.kind.toGeary() - val target: Entity = relation.target.toGeary() + val kind: EntityId = relation.kind + val target: EntityId = relation.target } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentAccessor.kt index 58823cfa8..81f1dacff 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentAccessor.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentAccessor.kt @@ -5,15 +5,16 @@ import androidx.collection.mutableObjectListOf import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.family.family +import com.mineinabyss.geary.engine.ComponentProvider import com.mineinabyss.geary.engine.archetypes.Archetype import com.mineinabyss.geary.systems.accessors.Accessor import com.mineinabyss.geary.systems.accessors.FamilyMatching import com.mineinabyss.geary.systems.accessors.ReadWriteAccessor import com.mineinabyss.geary.systems.query.Query -import kotlin.reflect.KProperty @OptIn(UnsafeAccessors::class) class ComponentAccessor( + comp: ComponentProvider, override val originalAccessor: Accessor?, val id: ComponentId ) : ReadWriteAccessor, FamilyMatching { diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsAccessor.kt index 80f590b0f..9fd63f2e0 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsAccessor.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsAccessor.kt @@ -5,6 +5,7 @@ import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.Relation import com.mineinabyss.geary.datatypes.family.family +import com.mineinabyss.geary.engine.ComponentProvider import com.mineinabyss.geary.engine.archetypes.Archetype import com.mineinabyss.geary.systems.accessors.Accessor import com.mineinabyss.geary.systems.accessors.FamilyMatching @@ -12,6 +13,7 @@ import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor import com.mineinabyss.geary.systems.query.Query class RelationsAccessor( + val comp: ComponentProvider, override val originalAccessor: Accessor?, val kind: ComponentId, val target: EntityId, diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsWithDataAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsWithDataAccessor.kt index 9508c23e9..babba7d06 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsWithDataAccessor.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsWithDataAccessor.kt @@ -5,6 +5,7 @@ import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.Relation import com.mineinabyss.geary.datatypes.family.family +import com.mineinabyss.geary.engine.ComponentProvider import com.mineinabyss.geary.engine.archetypes.Archetype import com.mineinabyss.geary.systems.accessors.Accessor import com.mineinabyss.geary.systems.accessors.FamilyMatching @@ -14,6 +15,7 @@ import com.mineinabyss.geary.systems.query.Query @OptIn(UnsafeAccessors::class) class RelationsWithDataAccessor( + val comp: ComponentProvider, override val originalAccessor: Accessor?, val kind: ComponentId, val target: EntityId, diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/GlobalFunctions.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/GlobalFunctions.kt deleted file mode 100644 index 7e40e3139..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/GlobalFunctions.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.mineinabyss.geary.systems.builders - -import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.modules.GearyModule -import com.mineinabyss.geary.observers.builders.ObserverWithData -import com.mineinabyss.geary.observers.builders.ObserverWithoutData -import com.mineinabyss.geary.systems.query.CachedQuery -import com.mineinabyss.geary.systems.query.Query - - -fun GearyModule.cache( - query: T, -): CachedQuery { - return queryManager.trackQuery(query) -} - -inline fun GearyModule.observe(): ObserverWithoutData { - return ObserverWithoutData(listOf(componentId()), this) { - eventRunner.addObserver(it) - } -} - -inline fun GearyModule.observeWithData(): ObserverWithData { - return ObserverWithData(listOf(componentId()), this) { - eventRunner.addObserver(it) - } -} - -fun GearyModule.system( - query: T -): SystemBuilder { - val defaultName = Throwable().stackTraceToString() - .lineSequence() - .drop(2) // First line error, second line is this function - .first() - .trim() - .substringBeforeLast("(") - .substringAfter("$") - .substringAfter("Kt.") - .substringAfter("create") - - return SystemBuilder(defaultName, query, pipeline) -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/CachedQuery.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/CachedQuery.kt index 8f5c402ed..399d83173 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/CachedQuery.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/CachedQuery.kt @@ -2,10 +2,12 @@ package com.mineinabyss.geary.systems.query import com.mineinabyss.geary.annotations.optin.ExperimentalGearyApi import com.mineinabyss.geary.annotations.optin.UnsafeAccessors +import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider import com.mineinabyss.geary.helpers.fastForEach -import com.mineinabyss.geary.modules.archetypes +import com.mineinabyss.geary.modules.get class CachedQuery internal constructor(val query: T) { val matchedArchetypes: MutableList = mutableListOf() @@ -68,7 +70,7 @@ class CachedQuery internal constructor(val query: T) { val accessors = cachingAccessors // current archetype - var archetype = archetypes.archetypeProvider.rootArchetype // avoid nullable perf loss + var archetype = query.world.get().rootArchetype // avoid nullable perf loss var upTo = 0 // current entity @@ -174,7 +176,8 @@ class CachedQuery internal constructor(val query: T) { inline fun mapWithEntity(crossinline run: T.(T) -> R): List> { val deferred = mutableListOf>() forEach { - deferred.add(Deferred(run(it), it.unsafeEntity)) + // TODO use EntityList instead + deferred.add(Deferred(run(it), Entity(it.unsafeEntity, world))) } return deferred } @@ -182,9 +185,15 @@ class CachedQuery internal constructor(val query: T) { @OptIn(UnsafeAccessors::class) fun entities(): List { val entities = mutableListOf() - forEach { entities.add(it.unsafeEntity) } + forEach { entities.add(Entity(it.unsafeEntity, world)) } return entities } + + fun count(): Int { + var count = 0 + forEach { count++ } + return count + } } inline fun List>.execOnFinish(run: (data: R, entity: GearyEntity) -> Unit) { diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt index 487022130..49066540b 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt @@ -1,19 +1,25 @@ package com.mineinabyss.geary.systems.query import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.engine.archetypes.Archetype -import com.mineinabyss.geary.modules.archetypes +import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider +import com.mineinabyss.geary.modules.Geary +import com.mineinabyss.geary.modules.get import com.mineinabyss.geary.systems.accessors.Accessor import com.mineinabyss.geary.systems.accessors.AccessorOperations import com.mineinabyss.geary.systems.accessors.FamilyMatching open class QueriedEntity( - override val cacheAccessors: Boolean -) : AccessorOperations() { + final override val world: Geary, + final override val cacheAccessors: Boolean, +) : AccessorOperations(), Geary by world { + @PublishedApi + @UnsafeAccessors + internal var archetype = world.get().rootArchetype internal val extraFamilies: MutableList = mutableListOf() @@ -34,10 +40,6 @@ open class QueriedEntity( cachingAccessors.forEach { it.updateCache(archetype) } } - @PublishedApi - @UnsafeAccessors - internal var archetype = archetypes.archetypeProvider.rootArchetype - @PublishedApi @UnsafeAccessors internal var row = 0 @@ -45,6 +47,6 @@ open class QueriedEntity( private var delegate: GearyEntity? = null @UnsafeAccessors - val unsafeEntity: Entity + val unsafeEntity: EntityId get() = this.archetype.getEntity(row) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/Query.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/Query.kt index 12631f7db..5043dfc73 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/Query.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/Query.kt @@ -1,10 +1,12 @@ package com.mineinabyss.geary.systems.query +import com.mineinabyss.geary.modules.ArchetypeEngineModule +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.systems.accessors.Accessor import com.mineinabyss.geary.systems.accessors.type.ComponentAccessor import kotlin.reflect.KProperty -abstract class Query : QueriedEntity(cacheAccessors = true) { +abstract class Query(world: Geary) : QueriedEntity(world, cacheAccessors = true) { /** Automatically matches families for any accessor that's supposed to match a family. */ operator fun T.provideDelegate( thisRef: Any, diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueryShorthands.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueryShorthands.kt index fd4748f7e..0a69a7550 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueryShorthands.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueryShorthands.kt @@ -5,20 +5,21 @@ import com.mineinabyss.geary.datatypes.entityTypeOf import com.mineinabyss.geary.datatypes.family.MutableFamily import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.helpers.cId +import com.mineinabyss.geary.modules.Geary import kotlin.jvm.JvmName -abstract class ShorthandQuery : Query() { +abstract class ShorthandQuery(world: Geary) : Query(world) { abstract val involves: EntityType } -abstract class ShorthandQuery1 : ShorthandQuery() { +abstract class ShorthandQuery1(world: Geary) : ShorthandQuery(world) { val comp1 get() = component1() abstract operator fun component1(): A } -abstract class ShorthandQuery2 : ShorthandQuery() { +abstract class ShorthandQuery2(world: Geary) : ShorthandQuery(world) { val comp1 get() = component1() val comp2 get() = component2() @@ -26,7 +27,7 @@ abstract class ShorthandQuery2 : ShorthandQuery() { abstract operator fun component2(): B } -abstract class ShorthandQuery3 : ShorthandQuery() { +abstract class ShorthandQuery3(world: Geary) : ShorthandQuery(world) { val comp1 get() = component1() val comp2 get() = component2() val comp3 get() = component3() @@ -36,7 +37,7 @@ abstract class ShorthandQuery3 : ShorthandQuery() { abstract operator fun component3(): C } -abstract class ShorthandQuery4 : ShorthandQuery() { +abstract class ShorthandQuery4(world: Geary) : ShorthandQuery(world) { val comp1 get() = component1() val comp2 get() = component2() val comp3 get() = component3() @@ -48,7 +49,7 @@ abstract class ShorthandQuery4 : ShorthandQuery() { abstract operator fun component4(): D } -abstract class ShorthandQuery5 : ShorthandQuery() { +abstract class ShorthandQuery5(world: Geary) : ShorthandQuery(world) { val comp1 get() = component1() val comp2 get() = component2() val comp3 get() = component3() @@ -63,16 +64,16 @@ abstract class ShorthandQuery5 : ShorthandQuery() { } -fun query() = object : Query() {} +fun Geary.query() = object : Query(this) {} -fun query(match: MutableFamily.Selector.And.() -> Unit) = object : Query() { +fun Geary.query(match: MutableFamily.Selector.And.() -> Unit) = object : Query(this) { override fun ensure() = this { add(family(match)) } } -inline fun query( +inline fun Geary.query( size1: QueryShorthands.Size1? = null, noinline filterFamily: (MutableFamily.Selector.And.() -> Unit)? = null -) = object : ShorthandQuery1() { +) = object : ShorthandQuery1(this) { override val involves = entityTypeOf(cId()) override fun ensure() { filterFamily?.let { this { it() } } @@ -83,10 +84,10 @@ inline fun query( override fun component1() = accessor1.get(this) } -inline fun query( +inline fun Geary.query( size2: QueryShorthands.Size2? = null, noinline filterFamily: (MutableFamily.Selector.And.() -> Unit)? = null, -) = object : ShorthandQuery2() { +) = object : ShorthandQuery2(this) { override val involves = entityTypeOf(cId(), cId()) override fun ensure() { filterFamily?.let { this { it() } } @@ -100,10 +101,10 @@ inline fun query( } -inline fun query( +inline fun Geary.query( size3: QueryShorthands.Size3? = null, noinline filterFamily: (MutableFamily.Selector.And.() -> Unit)? = null, -) = object : ShorthandQuery3() { +) = object : ShorthandQuery3(this) { override val involves = entityTypeOf(cId(), cId(), cId()) override fun ensure() { filterFamily?.let { this { it() } } @@ -118,10 +119,10 @@ inline fun query( override fun component3(): C = accessor3.get(this) } -inline fun query( +inline fun Geary.query( size4: QueryShorthands.Size4? = null, noinline filterFamily: (MutableFamily.Selector.And.() -> Unit)? = null, -) = object : ShorthandQuery4() { +) = object : ShorthandQuery4(this) { override val involves = entityTypeOf(cId(), cId(), cId(), cId()) override fun ensure() { filterFamily?.let { this { it() } } @@ -138,10 +139,10 @@ inline fun query( override fun component4(): D = accessor4.get(this) } -inline fun query( +inline fun Geary.query( size5: QueryShorthands.Size5? = null, noinline filterFamily: (MutableFamily.Selector.And.() -> Unit)? = null, -) = object : ShorthandQuery5() { +) = object : ShorthandQuery5(this) { override val involves = entityTypeOf(cId(), cId(), cId(), cId()) override fun ensure() { filterFamily?.let { this { it() } } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/components/ComponentAsEntityProviderTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/components/ComponentAsEntityProviderTest.kt new file mode 100644 index 000000000..2a4925660 --- /dev/null +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/components/ComponentAsEntityProviderTest.kt @@ -0,0 +1,15 @@ +package com.mineinabyss.geary.components + +import com.mineinabyss.geary.helpers.componentId +import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.test.GearyTest +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class ComponentAsEntityProviderTest: GearyTest() { + @Test + fun `should correctly register reserved components`() { + entity() + componentId() shouldBe ReservedComponents.ANY + } +} diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/EntityTypeTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/EntityTypeTest.kt index 1901d2f9f..fb6d0cfe7 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/EntityTypeTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/EntityTypeTest.kt @@ -1,6 +1,6 @@ package com.mineinabyss.geary.datatypes -import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.test.GearyTest import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/Family2ObjectArrayMapTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/Family2ObjectArrayMapTest.kt index 329408654..0877f213d 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/Family2ObjectArrayMapTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/Family2ObjectArrayMapTest.kt @@ -2,9 +2,11 @@ package com.mineinabyss.geary.datatypes import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.datatypes.maps.Family2ObjectArrayMap +import com.mineinabyss.geary.engine.ComponentProvider import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContainExactly import org.junit.jupiter.api.Test +import kotlin.reflect.KClassifier class Family2ObjectArrayMapTest { @Test diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/FamilyTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/FamilyTest.kt index e9040d324..f96dbeb14 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/FamilyTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/FamilyTest.kt @@ -4,7 +4,8 @@ import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.contains import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.test.GearyTest +import com.mineinabyss.geary.modules.relationOf import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test @@ -29,11 +30,11 @@ internal class FamilyTest: GearyTest() { hasRelation(target) } - (EntityType(listOf(Relation.of(other).id)) in family) shouldBe false - (EntityType(listOf(Relation.of(target).id)) in family) shouldBe true + (EntityType(listOf(relationOf(other).id)) in family) shouldBe false + (EntityType(listOf(relationOf(target).id)) in family) shouldBe true // Archetypes will always have a non HOLDS_DATA version of a component present, but this alone should not succeed - (EntityType(listOf(Relation.of(target).id)) in family) shouldBe false - (EntityType(listOf(Relation.of(target).id, target.id)) in family) shouldBe true + (EntityType(listOf(relationOf(target).id)) in family) shouldBe false + (EntityType(listOf(relationOf(target).id, target.id)) in family) shouldBe true } @Test @@ -44,9 +45,9 @@ internal class FamilyTest: GearyTest() { hasRelation(target) } - (EntityType(listOf(Relation.of(target).id)) in family) shouldBe false - (EntityType(listOf(Relation.of(other).withRole(HOLDS_DATA).id)) in family) shouldBe false - (EntityType(listOf(Relation.of(target).withRole(HOLDS_DATA).id)) in family) shouldBe true + (EntityType(listOf(relationOf(target).id)) in family) shouldBe false + (EntityType(listOf(relationOf(other).withRole(HOLDS_DATA).id)) in family) shouldBe false + (EntityType(listOf(relationOf(target).withRole(HOLDS_DATA).id)) in family) shouldBe true } @Test @@ -63,8 +64,8 @@ internal class FamilyTest: GearyTest() { } } - (EntityType(listOf(Relation.of(target).id)) in family) shouldBe false - (EntityType(listOf(componentId(), Relation.of(target).id)) in family) shouldBe true + (EntityType(listOf(relationOf(target).id)) in family) shouldBe false + (EntityType(listOf(componentId(), relationOf(target).id)) in family) shouldBe true (EntityType(listOf(componentId())) in family) shouldBe false (EntityType(listOf(componentId())) in family) shouldBe false diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/GearyEntityTests.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/GearyEntityTests.kt index 5253261a4..df7a475ea 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/GearyEntityTests.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/datatypes/GearyEntityTests.kt @@ -2,9 +2,10 @@ package com.mineinabyss.geary.datatypes import com.mineinabyss.geary.components.relations.InstanceOf import com.mineinabyss.geary.helpers.* -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.modules.archetypes +import com.mineinabyss.geary.test.GearyTest +import com.mineinabyss.geary.modules.relationOf import com.mineinabyss.geary.systems.accessors.RelationWithData +import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.shouldBe @@ -36,7 +37,7 @@ internal class GearyEntityTests : GearyTest() { entity { add() set(1) - }.type.getArchetype() shouldBe archetypes.archetypeProvider.rootArchetype + componentId() + componentId() + (HOLDS_DATA or componentId()) + }.type.getArchetype() shouldBe rootArchetype + componentId() + componentId() + (HOLDS_DATA or componentId()) } @Test @@ -44,7 +45,7 @@ internal class GearyEntityTests : GearyTest() { entity { set("Test") remove() - }.type.getArchetype() shouldBe archetypes.archetypeProvider.rootArchetype + }.type.getArchetype() shouldBe rootArchetype } @Test @@ -53,7 +54,7 @@ internal class GearyEntityTests : GearyTest() { set(1) set("Test") remove() - }.type.getArchetype() shouldBe archetypes.archetypeProvider.rootArchetype + componentId() + (HOLDS_DATA or componentId()) + }.type.getArchetype() shouldBe rootArchetype + componentId() + (HOLDS_DATA or componentId()) } @Test @@ -84,7 +85,7 @@ internal class GearyEntityTests : GearyTest() { entity { add() set("Test") - }.type.getArchetype() shouldBe archetypes.archetypeProvider.rootArchetype + componentId() + (componentId() or HOLDS_DATA) + }.type.getArchetype() shouldBe rootArchetype + componentId() + (componentId() or HOLDS_DATA) } @Test @@ -126,8 +127,8 @@ internal class GearyEntityTests : GearyTest() { setRelation("String to int relation") } entity.type.inner.shouldContainExactlyInAnyOrder( - Relation.of().id, - Relation.of().id, + relationOf().id, + relationOf().id, ) } @@ -141,7 +142,7 @@ internal class GearyEntityTests : GearyTest() { } entity.getAll().shouldContainExactlyInAnyOrder( "Test", - RelationWithData(RelatesTo(), null, relation = Relation.of().withRole(HOLDS_DATA)), + RelationWithData(RelatesTo(), null, relation = relationOf().withRole(HOLDS_DATA)), ) } @@ -163,8 +164,8 @@ internal class GearyEntityTests : GearyTest() { addParent(parent) } - parent.children.shouldContainExactly(child) - child.parents.shouldContainExactly(parent) + parent.children.shouldContain(child) + child.parents.shouldContain(parent) } } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/engine/ArchetypeTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/engine/ArchetypeTest.kt index 983bef0a0..81b89214d 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/engine/ArchetypeTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/engine/ArchetypeTest.kt @@ -3,12 +3,15 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.components.relations.InstanceOf import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.datatypes.Relation +import com.mineinabyss.geary.datatypes.entityTypeOf +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.getArchetype -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.modules.archetypes +import com.mineinabyss.geary.modules.get +import com.mineinabyss.geary.test.GearyTest +import com.mineinabyss.geary.modules.relationOf import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Nested @@ -19,7 +22,7 @@ internal class ArchetypeTest : GearyTest() { @Nested inner class ArchetypeNavigation { - val root = archetypes.archetypeProvider.rootArchetype + val root = rootArchetype @Test fun `archetype ids assigned correctly`() { @@ -51,10 +54,10 @@ internal class ArchetypeTest : GearyTest() { fun matchedRelations() { val target = entity() val target2 = entity() - val relatesTo = Relation.of(target) - val instanceOf = Relation.of(target) - val instanceOf2 = Relation.of(target2) - val arc = Archetype(EntityType(listOf(relatesTo.id, instanceOf.id, instanceOf2.id)), 0) + val relatesTo = relationOf(target) + val instanceOf = relationOf(target) + val instanceOf2 = relationOf(target2) + val arc = get().getArchetype(entityTypeOf(relatesTo.id, instanceOf.id, instanceOf2.id)) arc.getRelationsByTarget(target.id).map { Relation.of(it) } .shouldContainExactlyInAnyOrder(relatesTo, instanceOf) arc.getRelationsByKind(componentId()).map { Relation.of(it) } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/engine/GearyEngineTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/engine/GearyEngineTest.kt index 86865fd75..1ddbbdfc7 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/engine/GearyEngineTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/engine/GearyEngineTest.kt @@ -1,9 +1,7 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.helpers.toGeary -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.test.GearyTest import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -22,7 +20,7 @@ internal class GearyEngineTest : GearyTest() { entity().id shouldBe offset + 100uL (0 until 100).forEach { - geary.entityProvider.remove((offset + it.toULong()).toGeary()) + entityRemoveProvider.remove((offset + it.toULong())) } } } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyEntityWithOperatorTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyEntityWithOperatorTest.kt index 6243a9896..9fd3cb587 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyEntityWithOperatorTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyEntityWithOperatorTest.kt @@ -1,6 +1,6 @@ package com.mineinabyss.geary.helpers -import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.test.GearyTest import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyTestTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyTestTest.kt index d708f411f..02ab589a7 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyTestTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyTestTest.kt @@ -1,15 +1,28 @@ package com.mineinabyss.geary.helpers -import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap +import com.mineinabyss.geary.test.GearyTest import com.mineinabyss.geary.modules.geary +import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import org.junit.jupiter.api.Test +import org.koin.dsl.koinApplication +import org.koin.dsl.module class GearyTestTest: GearyTest() { @Test fun `clear engine`() { - val engine = geary.engine + val engine = this.engine resetEngine() - geary.engine shouldNotBe engine + this.engine shouldNotBe engine + } + + @Test + // This is testing a behaviour of koin that I didn't expect, keep this here in case it changes in the future + fun `reusing koin modules should reuse exact class instances`() { + val module = module { + single { ArrayTypeMap() } + } + koinApplication { modules(module) }.koin.get() shouldBe koinApplication { modules(module) }.koin.get() } } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyTypeFamilyHelpersTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyTypeFamilyHelpersTest.kt index 80537d03b..9e8277a7d 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyTypeFamilyHelpersTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/GearyTypeFamilyHelpersTest.kt @@ -3,7 +3,7 @@ package com.mineinabyss.geary.helpers import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.datatypes.Relation import com.mineinabyss.geary.datatypes.family.MutableFamily -import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.test.GearyTest import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/tests/GearyTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/tests/GearyTest.kt index 8cc63eb7f..e2f8242ab 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/tests/GearyTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/helpers/tests/GearyTest.kt @@ -1,44 +1,13 @@ package com.mineinabyss.geary.helpers.tests +import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider +import com.mineinabyss.geary.modules.Geary import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.idofront.di.DI +import com.mineinabyss.geary.modules.get import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.withContext import org.junit.jupiter.api.AfterAll - -abstract class GearyTest { - init { - startEngine() - } - - fun startEngine() { - geary(TestEngineModule) - } - - @AfterAll - fun clearEngine() { - DI.clear() - } - - /** Recreates the engine. */ - fun resetEngine() { - clearEngine() - startEngine() - } - - companion object { - suspend inline fun concurrentOperation( - times: Int = 10000, - crossinline run: suspend (id: Int) -> T - ): List> { - return withContext(Dispatchers.Default) { - (0 until times).map { id -> - async { run(id) } - } - } - } - } -} +import org.koin.core.KoinApplication diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/instancing/InstancingTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/instancing/InstancingTest.kt index add1e1f7a..bc0789f78 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/instancing/InstancingTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/instancing/InstancingTest.kt @@ -1,12 +1,11 @@ package com.mineinabyss.geary.instancing import com.mineinabyss.geary.components.relations.NoInherit -import com.mineinabyss.geary.datatypes.Relation import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.test.GearyTest +import com.mineinabyss.geary.modules.observe +import com.mineinabyss.geary.modules.relationOf import com.mineinabyss.geary.observers.events.OnSet -import com.mineinabyss.geary.systems.builders.observe import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import org.junit.jupiter.api.BeforeEach @@ -30,7 +29,7 @@ class InstancingTest : GearyTest() { val instance = entity { extend(prefab) } // assert - instance.getRelations().shouldBe(listOf(Relation.of(relatesTo))) + instance.getRelations().shouldBe(listOf(relationOf(relatesTo))) } @Test @@ -68,7 +67,7 @@ class InstancingTest : GearyTest() { @Test fun `should correctly extend entity when listener modifies it during extend process`() { // arrange - geary.observe().involving().exec { + observe().involving().exec { entity.set("Modifying!") } val prefab = entity { diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/koin/ArchetypeEngineModuleCheck.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/koin/ArchetypeEngineModuleCheck.kt new file mode 100644 index 000000000..d6edd4663 --- /dev/null +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/koin/ArchetypeEngineModuleCheck.kt @@ -0,0 +1,40 @@ +package com.mineinabyss.geary.koin + +import co.touchlab.kermit.LoggerConfig +import com.mineinabyss.geary.engine.archetypes.operations.ArchetypeReadOperations +import com.mineinabyss.geary.modules.ArchetypeEngineModule +import com.mineinabyss.geary.modules.ArchetypesModules +import com.mineinabyss.geary.modules.TestEngineModule +import com.mineinabyss.geary.modules.geary +import org.junit.jupiter.api.Test +import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.core.module.Module +import org.koin.dsl.koinApplication +import org.koin.test.check.checkModules +import org.koin.test.verify.verify +import kotlin.coroutines.CoroutineContext +import kotlin.time.Duration + +class ArchetypeEngineModuleCheck { + @OptIn(KoinExperimentalAPI::class) + @Test + fun checkKoinModule() { + ArchetypeEngineModule().module.verify( + extraTypes = listOf(Boolean::class, Duration::class, LoggerConfig::class, CoroutineContext::class) + ) + } + + @Test + fun createKoinModule() { + koinApplication { + properties(ArchetypeEngineModule().properties) + modules(ArchetypeEngineModule().module) + checkModules() + } + } + + @Test + fun startGeary() { + geary(TestEngineModule).start() + } +} diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/EntityRemoveObserverTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/EntityRemoveObserverTest.kt index de8bd2a03..702911d13 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/EntityRemoveObserverTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/EntityRemoveObserverTest.kt @@ -1,10 +1,9 @@ package com.mineinabyss.geary.observers import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.test.GearyTest +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.events.OnEntityRemoved -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query import io.kotest.matchers.shouldBe import kotlin.test.Test @@ -14,13 +13,13 @@ class EntityRemoveObserverTest : GearyTest() { fun `should correctly run multiple listeners on single event`() { var called = 0 - val listener1 = geary.observe().exec(query()) { (data) -> + val listener1 = observe().exec(query()) { (data) -> data shouldBe 1 entity.remove() called++ } - val listener2 = geary.observe().exec(query()) { (data) -> + val listener2 = observe().exec(query()) { (data) -> data shouldBe "" } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/ObserveComponentEventsTests.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/ObserveComponentEventsTests.kt index a82f12c59..292a1c480 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/ObserveComponentEventsTests.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/ObserveComponentEventsTests.kt @@ -2,12 +2,11 @@ package com.mineinabyss.geary.observers import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.test.GearyTest +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.events.OnAdd import com.mineinabyss.geary.observers.events.OnRemove import com.mineinabyss.geary.observers.events.OnSet -import com.mineinabyss.geary.systems.builders.observe import com.mineinabyss.geary.systems.query.query import io.kotest.assertions.assertSoftly import io.kotest.matchers.collections.shouldContainExactly @@ -28,7 +27,7 @@ internal class ObserveComponentEventsTests : GearyTest() { @Test fun `should correctly observe OnSet events involving single component`() { var called = 0 - geary.observe().involving().exec { called += 1 } + observe().involving().exec { called += 1 } val entity = entity() called shouldBe 0 @@ -42,7 +41,7 @@ internal class ObserveComponentEventsTests : GearyTest() { fun `should correctly observe OnSet events involving multiple components`() { var called = 0 - geary.observe().involving(query()).exec { called++ } + observe().involving(query()).exec { called++ } entity { set("") @@ -63,7 +62,7 @@ internal class ObserveComponentEventsTests : GearyTest() { @Test fun `should observe add event when component added or set`() { var called = 0 - geary.observe().involving().exec { + observe().involving().exec { called += 1 } @@ -81,7 +80,7 @@ internal class ObserveComponentEventsTests : GearyTest() { fun `should fire remove listener and have access to removed component data when component removed`() { // arrange var called = 0 - geary.observe().involving().exec(query()) { (string) -> + observe().involving().exec(query()) { (string) -> string shouldBe "data" called++ } @@ -102,7 +101,7 @@ internal class ObserveComponentEventsTests : GearyTest() { var called = 0 - geary.observe().involving().exec { + observe().involving().exec { called++ } val entity = entity { @@ -121,7 +120,7 @@ internal class ObserveComponentEventsTests : GearyTest() { fun `should not call remove listener when added but not set component removed`() { // arrange var called = 0 - geary.observe().involving(query()).exec { + observe().involving(query()).exec { called++ } val entity = entity { @@ -139,7 +138,7 @@ internal class ObserveComponentEventsTests : GearyTest() { fun `should still remove component after remove listener modifies it`() { // arrange var called = 0 - geary.observe().involving(query()).exec { + observe().involving(query()).exec { called++ entity.set("new data") } @@ -161,7 +160,7 @@ internal class ObserveComponentEventsTests : GearyTest() { fun `should correctly fire listener that listens to several removed components`() { // arrange var called = mutableListOf() - geary.observe().involving(query()).exec { (string, int) -> + observe().involving(query()).exec { (string, int) -> called.add(entity) } val entity1 = entity { @@ -182,14 +181,14 @@ internal class ObserveComponentEventsTests : GearyTest() { entity3.remove() // Doesn't fire // assert - called shouldContainExactly listOf(entity1, entity2) + called shouldContainExactly listOf(entity1, entity2) } @Test fun `should correctly remove component if event listener modifies type`() { // arrange var called = 0 - geary.observe().involving(query()).exec { (string) -> + observe().involving(query()).exec { (string) -> called++ entity.set(1) } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/ObserverTypeTests.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/ObserverTypeTests.kt index a551a811b..16a437cf8 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/ObserverTypeTests.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/ObserverTypeTests.kt @@ -2,10 +2,10 @@ package com.mineinabyss.geary.observers import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.test.GearyTest import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.builders.observe -import com.mineinabyss.geary.systems.builders.observeWithData +import com.mineinabyss.geary.modules.observe +import com.mineinabyss.geary.modules.observeWithData import com.mineinabyss.geary.systems.query.query import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf @@ -22,7 +22,7 @@ class ObserverTypeTests : GearyTest() { @Test fun `should observe event regardless of holding data when not observing event data`() { var called = 0 - geary.observe().exec { called++ } + observe().exec { called++ } val entity = entity() called shouldBe 0 @@ -37,7 +37,7 @@ class ObserverTypeTests : GearyTest() { @Test fun `should not observe event without data when observing data`() { var called = 0 - geary.observeWithData().exec { + observeWithData().exec { event.shouldBeInstanceOf() called++ } @@ -55,7 +55,7 @@ class ObserverTypeTests : GearyTest() { @Test fun `should observe events involving component when filtering one involved component`() { var called = 0 - geary.observe().involving(query()).exec { (int) -> called += int } + observe().involving(query()).exec { (int) -> called += int } val entity = entity() diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/QueryAsMapTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/QueryAsMapTest.kt index ab2ab679c..7e45d4cd7 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/QueryAsMapTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/QueryAsMapTest.kt @@ -1,7 +1,7 @@ package com.mineinabyss.geary.observers import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.test.GearyTest import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.observers.queries.cacheAssociatedBy import com.mineinabyss.geary.systems.query.query @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test class QueryAsMapTest : GearyTest() { @Test fun `should correctly track entity in map`() { - val map = geary.cacheAssociatedBy(query()) { (string) -> string } + val map = cacheAssociatedBy(query()) { (string) -> string } entity { set("Hello world") diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/entity_scoped/EntityScopedObserverTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/entity_scoped/EntityScopedObserverTest.kt index ec5b73b7f..cd93e3472 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/entity_scoped/EntityScopedObserverTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/observers/entity_scoped/EntityScopedObserverTest.kt @@ -1,10 +1,9 @@ package com.mineinabyss.geary.observers.entity_scoped import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.test.GearyTest +import com.mineinabyss.geary.modules.observe import com.mineinabyss.geary.observers.entity.observe -import com.mineinabyss.geary.systems.builders.observe import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test @@ -18,7 +17,7 @@ class EntityScopedObserverTest : GearyTest() { val globallyObserve = entity() val lines = mutableListOf() scopedObserve.observe().exec { lines += "Scoped clicked $entity" } - geary.observe().exec { lines += "Global clicked $entity" } + observe().exec { lines += "Global clicked $entity" } // act scopedObserve.emit() diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/SimpleQueryTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/SimpleQueryTest.kt index a59d2db00..e68359bc5 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/SimpleQueryTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/SimpleQueryTest.kt @@ -2,7 +2,7 @@ package com.mineinabyss.geary.queries import com.mineinabyss.geary.annotations.optin.ExperimentalGearyApi import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.test.GearyTest import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.systems.query.Query import com.mineinabyss.geary.systems.query.query @@ -35,7 +35,7 @@ class SimpleQueryTest : GearyTest() { @Test fun `forEach should allow reading data`() { - val query = geary.queryManager.trackQuery(myQuery) + val query = queryManager.trackQuery(myQuery) val nums = mutableListOf() query.forEach { (int) -> @@ -46,7 +46,7 @@ class SimpleQueryTest : GearyTest() { @Test fun `entities should return matched entities correctly`() { - val query = geary.queryManager.trackQuery(myQuery) + val query = queryManager.trackQuery(myQuery) val nums = mutableListOf() query.entities().forEach { @@ -58,14 +58,14 @@ class SimpleQueryTest : GearyTest() { @Test fun `first should correctly return first matched entity`() { - val query = geary.queryManager.trackQuery(myQuery) + val query = queryManager.trackQuery(myQuery) query.find({ it.comp1 }, { it.comp1 == 5 }) shouldBe 5 } @Test fun `any should correctly check for matches if matched`() { - val query = geary.queryManager.trackQuery(myQuery) + val query = queryManager.trackQuery(myQuery) query.any { it.comp1 == 5 } shouldBe true query.any { it.comp1 == 100 } shouldBe false @@ -73,7 +73,7 @@ class SimpleQueryTest : GearyTest() { @Test fun `should be able to collect query as sequence`() { - val query = geary.queryManager.trackQuery(myQuery) + val query = queryManager.trackQuery(myQuery) query.collect { filter { it.comp1 % 2 == 0 }.map { it.comp1.toString() }.toList() @@ -82,7 +82,7 @@ class SimpleQueryTest : GearyTest() { @Test fun `should allow collecting fancier sequences`() { - val query = geary.queryManager.trackQuery(myQuery) + val query = queryManager.trackQuery(myQuery) query.collect { filter { it.comp1 % 2 == 0 }.map { it.comp1 }.sortedByDescending { it }.take(3).toList() @@ -91,7 +91,7 @@ class SimpleQueryTest : GearyTest() { @Test fun `should not allow working on sequence outside collect block`() { - val query = geary.queryManager.trackQuery(myQuery) + val query = queryManager.trackQuery(myQuery) shouldThrow { query.collect { @@ -109,7 +109,7 @@ class SimpleQueryTest : GearyTest() { entity { set("Only string") } - val query = geary.queryManager.trackQuery(query()) + val query = queryManager.trackQuery(query()) val matched = query.toList() assertSoftly(matched) { shouldContainAll((0..9 step 2).map { it to null }) diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/accessors/AccessorDataModificationTests.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/accessors/AccessorDataModificationTests.kt index b26c59f8e..c4b1e0d39 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/accessors/AccessorDataModificationTests.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/accessors/AccessorDataModificationTests.kt @@ -1,17 +1,14 @@ package com.mineinabyss.geary.queries.accessors -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.helpers.Comp1 import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.builders.cache +import com.mineinabyss.geary.test.GearyTest import com.mineinabyss.geary.systems.query.Query import io.kotest.matchers.shouldBe import kotlin.test.Test class AccessorDataModificationTests : GearyTest() { - private fun registerQuery() = geary.cache(object : Query() { + private fun registerQuery() = cache(object : Query(this) { var data by get() }) diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/accessors/MappedAccessorTests.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/accessors/MappedAccessorTests.kt index 56bd0dee3..85a7a0780 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/accessors/MappedAccessorTests.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/accessors/MappedAccessorTests.kt @@ -1,9 +1,7 @@ package com.mineinabyss.geary.queries.accessors import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.builders.cache +import com.mineinabyss.geary.test.GearyTest import com.mineinabyss.geary.systems.query.Query import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.shouldBe @@ -12,11 +10,11 @@ import org.junit.jupiter.api.Test internal class MappedAccessorTests : GearyTest() { private class Marker - private fun mappedQuery() = geary.cache(object : Query() { + private fun mappedQuery() = cache(object : Query(this) { val mapped by get().map { it.toString() } }) - private fun defaultingQuery() = geary.cache(object : Query() { + private fun defaultingQuery() = cache(object : Query(this) { val default by get().orDefault { "empty!" } override fun ensure() = this { has() } }) diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/FamilyMatchingTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/FamilyMatchingTest.kt index 8ba6c4991..d4c7e2c5c 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/FamilyMatchingTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/FamilyMatchingTest.kt @@ -1,15 +1,10 @@ package com.mineinabyss.geary.systems -import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.datatypes.HOLDS_DATA -import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.getArchetype -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.modules.archetypes -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.builders.system +import com.mineinabyss.geary.modules.findEntities +import com.mineinabyss.geary.test.GearyTest import com.mineinabyss.geary.systems.query.Query import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.collections.shouldContainAll @@ -20,7 +15,7 @@ class FamilyMatchingTest : GearyTest() { val stringId = componentId() or HOLDS_DATA val intId = componentId() - val system = geary.system(object : Query() { + val system = system(object : Query(this) { val string by get() override fun ensure() = this { has() } }).defer { it.string }.onFinish { data, entity -> @@ -28,7 +23,7 @@ class FamilyMatchingTest : GearyTest() { entity.has() shouldBe true } - val root = archetypes.archetypeProvider.rootArchetype + val root = rootArchetype val correctArchetype = root + stringId + intId @Test @@ -46,7 +41,8 @@ class FamilyMatchingTest : GearyTest() { set("Test") set(1) } - geary.queryManager.getEntitiesMatching(system.runner.family).shouldContainAll(entity, entity2) + val entities = findEntities(system.runner.family) + entities.shouldContainAll(entity, entity2) } @Test diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/RelationMatchingSystemTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/RelationMatchingSystemTest.kt index 30a116ea5..644300f0f 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/RelationMatchingSystemTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/RelationMatchingSystemTest.kt @@ -1,12 +1,12 @@ package com.mineinabyss.geary.systems import com.mineinabyss.geary.components.relations.InstanceOf +import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.contains import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.getArchetype -import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.modules.findEntities +import com.mineinabyss.geary.test.GearyTest import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.builders.system import com.mineinabyss.geary.systems.query.Query import io.kotest.inspectors.forAll import io.kotest.matchers.collections.shouldContain @@ -24,7 +24,7 @@ class RelationMatchingSystemTest : GearyTest() { fun relations() { var ran = 0 resetEngine() - val system = geary.system(object : Query() { + val system = system(object : Query(this) { val persists by getRelationsWithData() }).exec { ran++ @@ -57,7 +57,7 @@ class RelationMatchingSystemTest : GearyTest() { var ran = 0 var persistsCount = 0 var instanceOfCount = 0 - val system = geary.system(object : Query() { + val system = system(object : Query(this) { val persists by getRelationsWithData() val instanceOf by getRelationsWithData() }).exec { @@ -99,7 +99,6 @@ class RelationMatchingSystemTest : GearyTest() { fun relationsWithData() { resetEngine() - val entity = entity { setRelation(Persists()) add() @@ -110,16 +109,19 @@ class RelationMatchingSystemTest : GearyTest() { set("Test") } - val system = geary.system(object : Query() { + val system = system(object : Query(this) { val withData by getRelationsWithData() }).exec { withData.forAll { it.data shouldBe Persists() } withData.forAll { it.targetData shouldBe "Test" } } - + println(componentId()) + println(findEntities { + hasRelation() + }.toList()) system.runner.matchedArchetypes.shouldNotContain(entity.type.getArchetype()) system.runner.matchedArchetypes.shouldContain(entityWithData.type.getArchetype()) - geary.engine.tick(0) + engine.tick(0) } } diff --git a/geary-test/build.gradle.kts b/geary-test/build.gradle.kts new file mode 100644 index 000000000..8f7e8b3e9 --- /dev/null +++ b/geary-test/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id(idofrontLibs.plugins.mia.kotlin.jvm.get().pluginId) + id(idofrontLibs.plugins.mia.publication.get().pluginId) + alias(idofrontLibs.plugins.kotlinx.serialization) +} + +dependencies { + implementation(project(":geary-core")) + implementation(kotlin("test")) + implementation(idofrontLibs.kotlinx.coroutines.test) + implementation(libs.koin.test) + compileOnly(idofrontLibs.junit.jupiter) +} diff --git a/geary-test/src/main/kotlin/com/mineinabyss/geary/test/GearyTest.kt b/geary-test/src/main/kotlin/com/mineinabyss/geary/test/GearyTest.kt new file mode 100644 index 000000000..63bc700bc --- /dev/null +++ b/geary-test/src/main/kotlin/com/mineinabyss/geary/test/GearyTest.kt @@ -0,0 +1,53 @@ +package com.mineinabyss.geary.test + +import com.mineinabyss.geary.engine.Engine +import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider +import com.mineinabyss.geary.modules.* +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.withContext +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.TestInstance +import org.koin.core.KoinApplication + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract class GearyTest : Geary { + private var _application: KoinApplication? = null + override val application get() = _application!! + val rootArchetype get() = get().rootArchetype + + open fun setupGeary() = geary(TestEngineModule) + + init { + startEngine() + } + + fun startEngine(override: UninitializedGearyModule? = null) { + _application = (override ?: setupGeary()).start().application + } + + @AfterAll + fun clearEngine() { + _application = null + } + + /** Recreates the engine. */ + fun resetEngine(override: UninitializedGearyModule? = null) { + clearEngine() + startEngine(override) + } + + companion object { + suspend inline fun concurrentOperation( + times: Int = 10000, + crossinline run: suspend (id: Int) -> T, + ): List> { + return withContext(Dispatchers.Default) { + (0 until times).map { id -> + async { run(id) } + } + } + } + } +} diff --git a/gradle.properties b/gradle.properties index d28ee949a..407c8c1b4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ group=com.mineinabyss version=0.26 # Workaround for dokka builds failing on CI, see https://github.com/Kotlin/dokka/issues/1405 #org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m -idofrontVersion=0.25.3 +idofrontVersion=0.25.13 kotlin.native.ignoreDisabledTargets=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 08dfbc807..9fc848ee6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,8 @@ kotlinxBenchmarkRuntime = "0.4.10" okio = "3.9.0" roaringbitmap = "1.0.6" statelyConcurrency = "2.0.7" -uuid = "0.8.4" +koin = "4.0.0" +junitJupiter = "5.8.1" [libraries] androidx-collection = { module = "androidx.collection:collection", version.ref = "androidxCollection" } @@ -14,4 +15,6 @@ kotlinx-benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark- okio = { module = "com.squareup.okio:okio", version.ref = "okio" } roaringbitmap = { module = "org.roaringbitmap:RoaringBitmap", version.ref = "roaringbitmap" } stately-concurrency = { module = "co.touchlab:stately-concurrency", version.ref = "statelyConcurrency" } -uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } +koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } +koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } +junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junitJupiter" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f..a4b76b953 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index 1b6c78733..f5feea6d6 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,12 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +134,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +217,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32c..9d21a2183 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle.kts b/settings.gradle.kts index eb2b95f4d..f625799c5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,8 +26,9 @@ dependencyResolutionManagement { } include( - "geary-benchmarks", +// "geary-benchmarks", "geary-core", + "geary-test", ) // Go through addons directory and load all projects based on file name