Skip to content

Commit

Permalink
Feat: Listen to entity extensions
Browse files Browse the repository at this point in the history
Feat: reimplement CopyToInstances
  • Loading branch information
0ffz committed Feb 1, 2024
1 parent 917369c commit 6ad9a51
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.mineinabyss.geary.datatypes.Entity
import com.mineinabyss.geary.datatypes.GearyComponent
import com.mineinabyss.geary.helpers.entity
import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances
import com.mineinabyss.geary.prefabs.configuration.components.Prefab
import com.mineinabyss.geary.prefabs.helpers.inheritPrefabs
import com.mineinabyss.geary.serialization.dsl.serializableComponents
Expand Down Expand Up @@ -67,6 +68,7 @@ class PrefabLoader {
val entity = writeTo ?: entity()
entity.addRelation<NoInherit, Prefab>()
entity.addRelation<NoInherit, Uuid>()
entity.addRelation<NoInherit, CopyToInstances>()
entity.set(Prefab(path))
decoded.getOrNull()?.let { entity.setAll(it) }
entity.set(key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ 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.ParseChildOnPrefab
import com.mineinabyss.geary.prefabs.configuration.systems.ParseChildrenOnPrefab
import com.mineinabyss.geary.prefabs.configuration.systems.ParseRelationOnPrefab
import com.mineinabyss.geary.prefabs.configuration.systems.ParseRelationWithDataSystem
import com.mineinabyss.geary.prefabs.configuration.systems.*
import com.mineinabyss.geary.prefabs.systems.TrackPrefabsByKeySystem
import com.mineinabyss.idofront.di.DI

Expand All @@ -32,6 +29,7 @@ interface Prefabs {
ParseRelationOnPrefab(),
ParseRelationWithDataSystem(),
TrackPrefabsByKeySystem(),
CopyToInstancesSystem(),
)
geary.pipeline.runOnOrAfter(GearyPhase.INIT_ENTITIES) {
loader.loadOrUpdatePrefabs()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.mineinabyss.geary.prefabs.configuration.components
import com.mineinabyss.geary.datatypes.Component
import com.mineinabyss.geary.datatypes.Entity
import com.mineinabyss.geary.serialization.dsl.serializableComponents
import com.mineinabyss.geary.serialization.serializers.SerializedComponents
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
Expand All @@ -17,20 +18,38 @@ import kotlinx.serialization.Serializable
@Serializable
@SerialName("geary:copy_to_instances")
data class CopyToInstances(
private val temporary: Set<@Polymorphic Component> = setOf(),
private val persisting: Set<@Polymorphic Component> = setOf(),
private val temporary: SerializedComponents? = null,
private val persisting: SerializedComponents? = null,
) {
@Serializable
private data class DeepCopy(
val temporary: List<@Polymorphic Component>?,
val persisting: List<@Polymorphic Component>?
)

val formats get() = serializableComponents.formats

// This is the safest and cleanest way to deep-copy, even if a little performance intense.
private val serializedComponents by lazy { formats.binaryFormat.encodeToByteArray(serializer(), this) }
private val serializedComponents by lazy {
formats.binaryFormat.encodeToByteArray(
DeepCopy.serializer(),
DeepCopy(temporary, persisting)
)
}

private fun getDeepCopied() = formats.binaryFormat.decodeFromByteArray(serializer(), serializedComponents)
private fun getDeepCopied() = formats.binaryFormat.decodeFromByteArray(
DeepCopy.serializer(), serializedComponents
)

fun decodeComponentsTo(entity: Entity, override: Boolean = true) {
fun decodeComponentsTo(entity: Entity) {
val (instance, persist) = getDeepCopied()
//order of addition specifies that persisting components should override all
entity.setAll(instance, override)
entity.setAllPersisting(persist, override)
if (instance != null) {
entity.setAll(instance, override = false)
}
if (persist != null) {
entity.setAllPersisting(persist, override = false)
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.mineinabyss.geary.prefabs.configuration.systems

import com.mineinabyss.geary.annotations.optin.UnsafeAccessors
import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances
import com.mineinabyss.geary.systems.GearyListener
import com.mineinabyss.geary.systems.accessors.Pointers

class CopyToInstancesSystem : GearyListener() {
private val Pointers.baseEntity by whenExtendedEntity()

@OptIn(UnsafeAccessors::class)
override fun Pointers.handle() {
val copy = baseEntity.get<CopyToInstances>() ?: return
copy.decodeComponentsTo(target.entity)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.mineinabyss.geary.prefabs

import com.mineinabyss.geary.components.relations.NoInherit
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.idofront.di.DI
import io.kotest.matchers.shouldBe
import kotlinx.serialization.builtins.serializer
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class CopyToInstancesTest {
private val testKey = PrefabKey.of("test:1")

@BeforeEach
fun createEngine() {
DI.clear()
geary(TestEngineModule) {
install(Prefabs)

serialization {
components {
component(String.serializer())
component(Int.serializer())
}
}
}
geary.pipeline.runStartupTasks()
}

@Test
fun `should correctly add temporary and persisting components with CopyToInstances`() {
// arrange
val prefab = entity {
set(
CopyToInstances(
temporary = listOf(42),
persisting = listOf("Hello world")
)
)
addRelation<NoInherit, CopyToInstances>()
}

// act
val instance = entity { extend(prefab) }

// assert
instance.get<String>() shouldBe "Hello world"
instance.get<Int>() shouldBe 42
instance.getAllPersisting() shouldBe listOf("Hello world")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import com.mineinabyss.geary.datatypes.GearyComponent
import com.mineinabyss.geary.serialization.ComponentSerializers.Companion.fromCamelCaseToSnakeCase
import com.mineinabyss.geary.serialization.ComponentSerializers.Companion.hasNamespace
import com.mineinabyss.geary.serialization.ProvidedNamespaces
import kotlinx.serialization.ContextualSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
Expand All @@ -15,6 +13,8 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.modules.SerializersModule

typealias SerializedComponents = @Serializable(with = PolymorphicListAsMapSerializer::class) List<@Polymorphic GearyComponent>

open class PolymorphicListAsMapSerializer<T : Any>(
serializer: KSerializer<T>,
) : KSerializer<List<T>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ sealed class AddedComponent
sealed class SetComponent

sealed class UpdatedComponent

sealed class ExtendedEntity
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package com.mineinabyss.geary.datatypes.family
import com.mineinabyss.geary.components.events.AddedComponent
import com.mineinabyss.geary.components.events.SetComponent
import com.mineinabyss.geary.components.events.UpdatedComponent
import com.mineinabyss.geary.modules.geary
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

inline fun family(init: MutableFamily.Selector.And.() -> Unit): Family {
return MutableFamily.Selector.And().apply(init)
Expand Down Expand Up @@ -129,6 +129,10 @@ sealed class MutableFamily : Family {
onAdd.hasRelation<SetComponent?>(id)
}

fun onExtendedEntity() {
onAdd.hasRelation(geary.components.extendedEntity, geary.components.any)
}

inline fun or(init: Or.() -> Unit) {
add(Or().apply(init))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Components {
val addedComponent = componentId<AddedComponent>()
val setComponent = componentId<SetComponent>()
val updatedComponent = componentId<UpdatedComponent>()
val extendedEntity = componentId<ExtendedEntity>()
val entityRemoved = componentId<EntityRemoved>()
val childOf = componentId<ChildOf>()
val instanceOf = componentId<InstanceOf>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,19 +293,21 @@ class Archetype internal constructor(
fun instantiateTo(
base: Record,
instance: Record,
callEvent: Boolean = true,
) {
instance.archetype.addComponent(instance, Relation.of<InstanceOf?>(base.entity).id, true)
type.filter { !it.holdsData() }.forEach {
val noInheritComponents = EntityType(getRelationsByKind(componentId<NoInherit>()).map { it.target })
type.filter { !it.holdsData() && it !in noInheritComponents }.forEach {
instance.archetype.addComponent(instance, it, true)
}
val noInheritComponents = EntityType(getRelationsByKind(componentId<NoInherit>()).map { it.target })
dataHoldingType.forEach {
if (it.withoutRole(HOLDS_DATA) in noInheritComponents) return@forEach
instance.archetype.setComponent(instance, it, get(base.row, it)!!, true)
}
base.entity.children.forEach {
it.addParent(instance.entity)
}
if (callEvent) callComponentModifyEvent(geary.components.extendedEntity, instance, base.entity.id)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mineinabyss.geary.modules

import com.mineinabyss.geary.datatypes.maps.SynchronizedTypeMap
import com.mineinabyss.geary.engine.archetypes.EntityByArchetypeProvider

/**
Expand All @@ -10,8 +11,10 @@ import com.mineinabyss.geary.engine.archetypes.EntityByArchetypeProvider
*/
class TestEngineModule(
reuseIDsAfterRemoval: Boolean = true,
useSynchronized: Boolean = false,
) : ArchetypeEngineModule() {
override val entityProvider = EntityByArchetypeProvider(reuseIDsAfterRemoval)
override val records = if (useSynchronized) SynchronizedTypeMap(super.records) else super.records

companion object : GearyModuleProviderWithDefault<TestEngineModule> {
override fun init(module: TestEngineModule) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.mineinabyss.geary.systems

import com.mineinabyss.geary.components.events.ExtendedEntity
import com.mineinabyss.geary.datatypes.GearyEntity
import com.mineinabyss.geary.helpers.componentId
import com.mineinabyss.geary.helpers.toGeary
import com.mineinabyss.geary.systems.accessors.*
import com.mineinabyss.geary.systems.accessors.type.ComponentAccessor

Expand All @@ -21,7 +24,7 @@ abstract class Listener : AccessorOperations(), System {
onStart()
}

private fun getIndexForHolder(holder: AccessorHolder): Int= when(holder) {
private fun getIndexForHolder(holder: AccessorHolder): Int = when (holder) {
target -> 0
event -> 1
source -> 2
Expand All @@ -41,23 +44,29 @@ abstract class Listener : AccessorOperations(), System {
}

/** Fires when an entity has a component of type [T] set or updated. */
inline fun <reified T: ComponentAccessor<A>, reified A> T.whenSetOnTarget(): ReadWriteEntitySelectingAccessor<T, A> {
inline fun <reified T : ComponentAccessor<A>, reified A> T.whenSetOnTarget(): ReadWriteEntitySelectingAccessor<T, A> {
event.mutableFamily.onSet(componentId<A>())
return this.on(target)
}

/** Fires when an entity has a component of type [T] set, only if it was not set before. */
inline fun <reified T: ComponentAccessor<A>, reified A> T.whenFirstSetOnTarget(): ReadWriteEntitySelectingAccessor<T, A> {
inline fun <reified T : ComponentAccessor<A>, reified A> T.whenFirstSetOnTarget(): ReadWriteEntitySelectingAccessor<T, A> {
event.mutableFamily.onFirstSet(componentId<A>())
return this.on(target)
}

/** Fires when an entity has a component of type [T] added, updates are not considered since no data changes. */
inline fun <reified T: ComponentAccessor<A>, reified A> T.whenAddedOnTarget(): ReadWriteEntitySelectingAccessor<T, A> {
inline fun <reified T : ComponentAccessor<A>, reified A> T.whenAddedOnTarget(): ReadWriteEntitySelectingAccessor<T, A> {
event.mutableFamily.onAdd(componentId<A>())
return this.on(event)
}

/** Fires when an entity has a component of type [T] added, updates are not considered since no data changes. */
fun whenExtendedEntity(): ReadOnlyEntitySelectingAccessor<ReadOnlyAccessor<GearyEntity>, GearyEntity> {
event.mutableFamily.onExtendedEntity()
return getRelations<ExtendedEntity?, Any?>().map { it.single().target.toGeary() }.on(event)
}

abstract fun Pointers.handle()
}

Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ abstract class GearyTest {
startEngine()
}

suspend inline fun concurrentOperation(
times: Int = 10000,
crossinline run: suspend (id: Int) -> Unit
): List<Deferred<*>> {
return withContext(Dispatchers.Default) {
(0 until times).map { id ->
async { run(id) }
companion object {
suspend inline fun <T> concurrentOperation(
times: Int = 10000,
crossinline run: suspend (id: Int) -> T
): List<Deferred<T>> {
return withContext(Dispatchers.Default) {
(0 until times).map { id ->
async { run(id) }
}
}
}
}
Expand Down

0 comments on commit 6ad9a51

Please sign in to comment.