├── docs ├── version ├── index.md ├── TOPBAR.md ├── config.yml ├── backend │ ├── index.md │ ├── resources.md │ ├── future-plans │ │ └── memory-structure.md │ └── core │ │ └── index.md ├── SUMMARY.md └── guide │ ├── setup.md │ ├── listeners.md │ ├── persisting-data.md │ └── systems │ └── families.md ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── action.md │ ├── condition.md │ └── system-behaviour.md └── workflows │ ├── publish-packages.yml │ ├── gradle-ci.yml │ └── docs.yml ├── addons ├── geary-prefabs │ ├── resources │ │ └── prefabs │ │ │ ├── prefabA.yml │ │ │ └── prefabB.yml │ ├── src │ │ └── com │ │ │ └── mineinabyss │ │ │ └── geary │ │ │ └── prefabs │ │ │ ├── events │ │ │ └── PrefabLoaded.kt │ │ │ ├── DeferredLoadException.kt │ │ │ ├── configuration │ │ │ └── components │ │ │ │ ├── ReEmitEvent.kt │ │ │ │ ├── Prefab.kt │ │ │ │ ├── RelationOnPrefab.kt │ │ │ │ ├── ChildOnPrefab.kt │ │ │ │ ├── InheritPrefabs.kt │ │ │ │ ├── ChildrenOnPrefab.kt │ │ │ │ ├── InstancesOnPrefab.kt │ │ │ │ └── CopyToInstances.kt │ │ │ ├── PrefabPath.kt │ │ │ ├── serializers │ │ │ └── PrefabKeySerializer.kt │ │ │ ├── PrefabManager.kt │ │ │ ├── helpers │ │ │ └── InheritPrefabs.kt │ │ │ ├── PrefabKey.kt │ │ │ └── PrefabsDSL.kt │ ├── build.gradle.kts │ └── test@jvm │ │ └── com │ │ └── mineinabyss │ │ └── geary │ │ └── prefabs │ │ ├── PrefabTests.kt │ │ ├── GearyEntityComponentIdSerializerTest.kt │ │ ├── CopyToInstancesTest.kt │ │ ├── PrefabFromResourcesTest.kt │ │ └── ComponentIdSerializerTest.kt ├── geary-actions │ ├── src │ │ └── com │ │ │ └── mineinabyss │ │ │ └── geary │ │ │ └── actions │ │ │ ├── ActionsCancelledException.kt │ │ │ ├── Condition.kt │ │ │ ├── event_binds │ │ │ ├── EventBind.kt │ │ │ ├── ParseEntityObservers.kt │ │ │ └── EntityObservers.kt │ │ │ ├── GearyActions.kt │ │ │ ├── Action.kt │ │ │ ├── expressions │ │ │ ├── FunctionExpressionWithInput.kt │ │ │ ├── EntityExpression.kt │ │ │ ├── InlineExpressionSerializer.kt │ │ │ └── FunctionExpression.kt │ │ │ ├── actions │ │ │ ├── EmitEventAction.kt │ │ │ ├── EvalAction.kt │ │ │ ├── BecomeAction.kt │ │ │ └── EnsureAction.kt │ │ │ ├── Tasks.kt │ │ │ ├── serializers │ │ │ ├── StructureDependantSerializer.kt │ │ │ └── DurationSerializer.kt │ │ │ └── ActionGroupContext.kt │ ├── README.md │ └── build.gradle.kts ├── geary-uuid │ ├── src │ │ └── com │ │ │ └── mineinabyss │ │ │ └── geary │ │ │ └── uuid │ │ │ ├── components │ │ │ └── RegenerateUUIDOnClash.kt │ │ │ ├── UUIDTracking.kt │ │ │ └── UUID2GearyMap.kt │ └── build.gradle.kts ├── geary-serialization │ ├── src │ │ └── com │ │ │ └── mineinabyss │ │ │ └── geary │ │ │ └── serialization │ │ │ ├── components │ │ │ └── Persists.kt │ │ │ ├── formats │ │ │ ├── Formats.kt │ │ │ ├── SimpleFormats.kt │ │ │ └── Format.kt │ │ │ ├── dsl │ │ │ ├── Aliases.kt │ │ │ ├── builders │ │ │ │ ├── ComponentSerializersBuilder.kt │ │ │ │ └── FormatsBuilder.kt │ │ │ └── WithCommonComponentNames.kt │ │ │ ├── helpers │ │ │ ├── Component.kt │ │ │ └── SerializationHelpers.kt │ │ │ ├── serializers │ │ │ ├── ProvidedConfig.kt │ │ │ ├── GearyEntitySerializer.kt │ │ │ ├── InnerSerializer.kt │ │ │ └── SerializableComponentId.kt │ │ │ └── ComponentSerializers.kt │ └── build.gradle.kts └── geary-autoscan │ ├── build.gradle.kts │ └── src │ └── com │ └── mineinabyss │ └── geary │ └── autoscan │ ├── AutoscanAnnotations.kt │ └── AutoScanner.kt ├── geary-core ├── resources │ └── junit-platform.properties ├── src │ └── com │ │ └── mineinabyss │ │ └── geary │ │ ├── engine │ │ ├── Aliases.kt │ │ ├── archetypes │ │ │ ├── ArchetypeProvider.kt │ │ │ ├── ComponentAsEntityProvider.kt │ │ │ ├── SimpleArchetypeProvider.kt │ │ │ └── ArchetypeEngine.kt │ │ ├── EntityProvider.kt │ │ ├── TickingEngine.kt │ │ ├── Engine.kt │ │ ├── EntityInfoReader.kt │ │ ├── Pipeline.kt │ │ ├── ComponentProvider.kt │ │ ├── QueryManager.kt │ │ ├── Components.kt │ │ ├── EntityReadOperations.kt │ │ ├── EntityMutateOperations.kt │ │ └── PipelineImpl.kt │ │ ├── components │ │ ├── CouldHaveChildren.kt │ │ ├── relations │ │ │ ├── ChildOf.kt │ │ │ ├── InstanceOf.kt │ │ │ └── NoInherit.kt │ │ ├── EntityName.kt │ │ ├── KeepEmptyArchetype.kt │ │ ├── ComponentInfo.kt │ │ └── ReservedComponents.kt │ │ ├── systems │ │ ├── query │ │ │ ├── QueryTypeAliases.kt │ │ │ ├── Query.kt │ │ │ └── QueriedEntity.kt │ │ ├── GearyAliases.kt │ │ ├── TrackedSystem.kt │ │ ├── accessors │ │ │ ├── FamilyMatching.kt │ │ │ ├── type │ │ │ │ ├── MappedAccessor.kt │ │ │ │ ├── ComponentOrDefaultAccessor.kt │ │ │ │ ├── ComponentAccessor.kt │ │ │ │ ├── RelationsAccessor.kt │ │ │ │ └── RelationsWithDataAccessor.kt │ │ │ ├── RelationWithData.kt │ │ │ └── Accessor.kt │ │ ├── System.kt │ │ └── builders │ │ │ ├── DeferredSystemBuilder.kt │ │ │ └── SystemBuilder.kt │ │ ├── observers │ │ ├── events │ │ │ ├── SuppressRemoveEvent.kt │ │ │ ├── EntityEvents.kt │ │ │ └── ComponentEvents.kt │ │ ├── builders │ │ │ └── ObserverContext.kt │ │ ├── EventRunner.kt │ │ ├── Observer.kt │ │ ├── EventToObserversMap.kt │ │ └── ObserverList.kt │ │ ├── modules │ │ ├── EngineInitializer.kt │ │ ├── Defaults.kt │ │ ├── TestEngineModule.kt │ │ ├── ArchetypeEngineInitializer.kt │ │ ├── GearyModule.kt │ │ ├── MutableAddons.kt │ │ └── GearySetup.kt │ │ ├── annotations │ │ └── optin │ │ │ ├── ExperimentalGearyApi.kt │ │ │ ├── DangerousComponentOperation.kt │ │ │ └── UnsafeAccessors.kt │ │ ├── datatypes │ │ ├── family │ │ │ ├── GearyAliases.kt │ │ │ └── Family.kt │ │ ├── CollectionHelpers.kt │ │ ├── README.md │ │ ├── EntityStack.kt │ │ ├── maps │ │ │ ├── TypeMap.kt │ │ │ ├── SynchronizedTypeMap.kt │ │ │ └── ArrayTypeMap.kt │ │ ├── BitSet.kt │ │ ├── GearyAliases.kt │ │ ├── BucketedULongArray.kt │ │ ├── TypeRoles.kt │ │ └── EntityIdArray.kt │ │ ├── addons │ │ ├── Namespaced.kt │ │ ├── dsl │ │ │ └── GearyDSL.kt │ │ └── GearyPhase.kt │ │ └── helpers │ │ ├── async │ │ ├── IgnoringAsyncCatcher.kt │ │ └── AsyncCatcher.kt │ │ ├── EntityHelpers.kt │ │ ├── ComponentHelpers.kt │ │ ├── GearyTypeFamilyHelpers.kt │ │ ├── Iteration.kt │ │ └── Relationship.kt ├── test@jvm │ └── com │ │ └── mineinabyss │ │ └── geary │ │ ├── helpers │ │ ├── Components.kt │ │ ├── tests │ │ │ └── GearyTest.kt │ │ ├── GearyEntityWithOperatorTest.kt │ │ ├── GearyTypeFamilyHelpersTest.kt │ │ ├── GearyTestTest.kt │ │ └── RoleHelperTest.kt │ │ ├── datatypes │ │ ├── EntityTypeTest.kt │ │ └── Family2ObjectArrayMapTest.kt │ │ ├── engine │ │ └── GearyEngineTest.kt │ │ ├── observers │ │ ├── QueryAsMapTest.kt │ │ ├── RemoveObserverTest.kt │ │ ├── EntityRemoveObserverTest.kt │ │ ├── EntityGetAsFlowTest.kt │ │ └── ObserverTypeTests.kt │ │ ├── queries │ │ ├── accessors │ │ │ ├── AccessorDataModificationTests.kt │ │ │ └── MappedAccessorTests.kt │ │ └── QueryForEachMutatingTest.kt │ │ ├── koin │ │ └── ArchetypeEngineModuleCheck.kt │ │ ├── systems │ │ └── FamilyMatchingTest.kt │ │ └── components │ │ └── ComponentAsEntityProviderTest.kt ├── src@native │ └── com │ │ └── mineinabyss │ │ └── geary │ │ └── datatypes │ │ └── BitSet.kt ├── build.gradle.kts └── src@jvm │ └── com │ └── mineinabyss │ └── geary │ └── datatypes │ └── BitSet.kt ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── geary-benchmarks ├── src │ └── com │ │ └── mineinabyss │ │ └── geary │ │ └── benchmarks │ │ ├── helpers │ │ ├── Values.kt │ │ ├── TestComponents.kt │ │ └── GearyBenchmark.kt │ │ ├── unpacking │ │ ├── Systems.kt │ │ ├── Unpack1Benchmark.kt │ │ ├── Unpack2Benchmark.kt │ │ └── Unpack6Benchmark.kt │ │ ├── instantiation │ │ └── ManyComponentsBenchmark.kt │ │ ├── events │ │ └── EventCalls.kt │ │ └── misc │ │ └── ComponentIdTest.kt └── build.gradle.kts ├── .gitignore ├── gradle.properties ├── geary-test ├── build.gradle.kts └── src │ └── com │ └── mineinabyss │ └── geary │ └── test │ └── GearyTest.kt ├── settings.gradle.kts └── LICENSE /docs/version: -------------------------------------------------------------------------------- 1 | 0.0.8 -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @0ffz 2 | -------------------------------------------------------------------------------- /addons/geary-prefabs/resources/prefabs/prefabA.yml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /addons/geary-prefabs/resources/prefabs/prefabB.yml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /docs/TOPBAR.md: -------------------------------------------------------------------------------- 1 | [:brand-github: GitHub](https://github.com/MineInAbyss/geary) 2 | -------------------------------------------------------------------------------- /docs/config.yml: -------------------------------------------------------------------------------- 1 | name: Geary docs 2 | siteUrl: https://docs.mineinabyss.com/geary -------------------------------------------------------------------------------- /geary-core/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.testinstance.lifecycle.default = per_class 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MineInAbyss/geary/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/Aliases.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | typealias GearyEngine = Engine 4 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/components/CouldHaveChildren.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.components 2 | 3 | sealed class CouldHaveChildren 4 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/components/relations/ChildOf.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.components.relations 2 | 3 | sealed class ChildOf 4 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/query/QueryTypeAliases.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.query 2 | 3 | typealias GearyQuery = Query 4 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/events/PrefabLoaded.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.events 2 | 3 | sealed class PrefabLoaded 4 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/components/relations/InstanceOf.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.components.relations 2 | 3 | sealed class InstanceOf 4 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/DeferredLoadException.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | class DeferredLoadException : Exception("") -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/observers/events/SuppressRemoveEvent.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers.events 2 | 3 | sealed class SuppressRemoveEvent 4 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/ActionsCancelledException.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions 2 | 3 | class ActionsCancelledException : Exception() 4 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/helpers/Values.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.helpers 2 | 3 | const val oneMil = 1000000 4 | const val tenMil = 10000000 5 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/modules/EngineInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.modules 2 | 3 | interface EngineInitializer { 4 | fun init() 5 | fun start() 6 | } 7 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/annotations/optin/ExperimentalGearyApi.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.annotations.optin 2 | 3 | @RequiresOptIn 4 | annotation class ExperimentalGearyApi 5 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/family/GearyAliases.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes.family 2 | 3 | typealias GearyFamily = Family 4 | typealias MutableGearyFamily = MutableFamily 5 | -------------------------------------------------------------------------------- /docs/backend/index.md: -------------------------------------------------------------------------------- 1 | # Backend 2 | 3 | This section focuses on the backend architecture and design of our engine and other systems. Part of its purpose is to help document decisions and let others learn from them when building their own ECS. 4 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/GearyAliases.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems 2 | 3 | import com.mineinabyss.geary.observers.Observer 4 | 5 | typealias GearyObserver = Observer 6 | typealias GearySystem = System 7 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/helpers/Components.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers 2 | 3 | data class Comp1(val id: Int) 4 | data class Comp2(val id: Int) 5 | data class Comp3(val id: Int) 6 | data class Comp4(val id: Int) 7 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/components/relations/NoInherit.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.components.relations 2 | 3 | /** Relation targets associated with [NoInherit] will not be copied onto prefab instances. */ 4 | sealed class NoInherit 5 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/observers/events/EntityEvents.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers.events 2 | 3 | import com.mineinabyss.geary.datatypes.EntityId 4 | 5 | class OnEntityRemoved 6 | 7 | class OnExtend(val baseEntity: EntityId) 8 | -------------------------------------------------------------------------------- /addons/geary-uuid/src/com/mineinabyss/geary/uuid/components/RegenerateUUIDOnClash.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.uuid.components 2 | 3 | /** 4 | * Instructs an entity to regenerate its uuid if it is already taken. 5 | */ 6 | sealed class RegenerateUUIDOnClash 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .gradle 3 | build 4 | target 5 | out 6 | */out/production/ 7 | .kotlin 8 | 9 | .classpath 10 | .project 11 | .settings 12 | eclipse 13 | *.ipr 14 | *.iws 15 | kotlin-js-store/ 16 | 17 | geary-benchmarks/.results 18 | /truckconfig.json 19 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/annotations/optin/DangerousComponentOperation.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.annotations.optin 2 | 3 | @RequiresOptIn("Likely unintentionally using list as a single component") 4 | annotation class DangerousComponentOperation 5 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=com.mineinabyss 2 | version=0.28 3 | # Workaround for dokka builds failing on CI, see https://github.com/Kotlin/dokka/issues/1405 4 | #org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m 5 | idofrontVersion=1.0.5 6 | kotlin.native.ignoreDisabledTargets=true 7 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/modules/Defaults.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.modules 2 | 3 | import kotlin.time.Duration 4 | import kotlin.time.Duration.Companion.milliseconds 5 | 6 | class Defaults( 7 | val repeatingSystemInterval: Duration = 50.milliseconds, 8 | ) 9 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/observers/events/ComponentEvents.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers.events 2 | 3 | sealed class OnAdd 4 | 5 | sealed class OnSet 6 | 7 | sealed class OnFirstSet 8 | 9 | sealed class OnRemove 10 | 11 | sealed class OnUpdate 12 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/Condition.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions 2 | 3 | interface Condition: Action { 4 | override fun ActionGroupContext.execute(): Boolean 5 | 6 | } 7 | 8 | fun Condition.execute(context: ActionGroupContext) = context.execute() 9 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/addons/Namespaced.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.addons 2 | 3 | import com.mineinabyss.geary.addons.dsl.GearyDSL 4 | import com.mineinabyss.geary.modules.GearySetup 5 | 6 | @GearyDSL 7 | class Namespaced(val namespace: String, val setup: GearySetup) 8 | 9 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/components/EntityName.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.components 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | /** 6 | * Represents a display name that should be presented to users. 7 | */ 8 | @JvmInline 9 | value class EntityName(val name: String) 10 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/components/KeepEmptyArchetype.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.components 2 | 3 | /** 4 | * An entity's archetype with this component may stay even if empty. 5 | * Useful for entities that are created and removed often. 6 | */ 7 | sealed class KeepEmptyArchetype 8 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/addons/dsl/GearyDSL.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.addons.dsl 2 | 3 | /** 4 | * A marker annotations for DSLs. 5 | */ 6 | @DslMarker 7 | @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) 8 | annotation class GearyDSL 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/components/ComponentInfo.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.components 2 | 3 | import kotlin.reflect.KClassifier 4 | 5 | /** 6 | * Represents the [class][kClass] a component entity is responsible for. 7 | */ 8 | data class ComponentInfo( 9 | val kClass: KClassifier 10 | ) 11 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/helpers/TestComponents.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.helpers 2 | 3 | data class Comp1(val id: Int) 4 | data class Comp2(val id: Int) 5 | data class Comp3(val id: Int) 6 | data class Comp4(val id: Int) 7 | data class Comp5(val id: Int) 8 | data class Comp6(val id: Int) 9 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/archetypes/ArchetypeProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine.archetypes 2 | 3 | import com.mineinabyss.geary.datatypes.EntityType 4 | 5 | interface ArchetypeProvider { 6 | val rootArchetype: Archetype 7 | 8 | fun getArchetype(entityType: EntityType): Archetype 9 | } 10 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/helpers/async/IgnoringAsyncCatcher.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers.async 2 | 3 | class IgnoringAsyncCatcher: AsyncCatcher { 4 | override fun isAsync(): Boolean = false 5 | 6 | override fun throwException(message: String) { 7 | throw IllegalStateException(message) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/observers/builders/ObserverContext.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers.builders 2 | 3 | import com.mineinabyss.geary.datatypes.Entity 4 | 5 | interface ObserverContext { 6 | val entity: Entity 7 | } 8 | 9 | interface ObserverContextWithData: ObserverContext { 10 | val event: R 11 | } 12 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/CollectionHelpers.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | import androidx.collection.LongSparseArray 4 | import androidx.collection.getOrElse 5 | 6 | fun LongSparseArray.getOrPut(key: Long, defaultValue: () -> T): T { 7 | return getOrElse(key) { defaultValue().also { put(key, it) } } 8 | } 9 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/helpers/async/AsyncCatcher.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers.async 2 | 3 | interface AsyncCatcher { 4 | fun isAsync(): Boolean 5 | 6 | fun throwException(message: String) 7 | } 8 | 9 | inline fun AsyncCatcher.catch(message: () -> String) { 10 | if (isAsync()) throwException(message()) 11 | } 12 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/helpers/GearyBenchmark.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.helpers 2 | 3 | import com.mineinabyss.geary.modules.Geary 4 | import com.mineinabyss.geary.modules.TestEngineModule 5 | import com.mineinabyss.geary.modules.geary 6 | 7 | abstract class GearyBenchmark : Geary by geary(TestEngineModule).start() 8 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/EntityProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.datatypes.Entity 4 | import com.mineinabyss.geary.datatypes.EntityId 5 | import com.mineinabyss.geary.datatypes.EntityType 6 | 7 | interface EntityProvider { 8 | /** Creates a new entity. */ 9 | fun create(): EntityId 10 | } 11 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/README.md: -------------------------------------------------------------------------------- 1 | # Geary Datastructures 2 | 3 | This package contains different datastructures designed by us. The goal is to have as much knowledge and control over parts of the engine that must be performant. 4 | 5 | By moving things into one place, we're able to more easily see what needs to be rewritten by us and what other libraries we depend on. 6 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/components/Persists.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.components 2 | 3 | /** 4 | * When related to another component, ensures it will be persisted if possible. 5 | * 6 | * @property hash Used to avoid unnecessary writes when data has not changed. 7 | */ 8 | data class Persists( 9 | var hash: Int = 0 10 | ) 11 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/TrackedSystem.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems 2 | 3 | import com.mineinabyss.geary.systems.query.CachedQuery 4 | import com.mineinabyss.geary.systems.query.Query 5 | 6 | class TrackedSystem( 7 | val system: System, 8 | val runner: CachedQuery 9 | ) { 10 | fun tick() { 11 | system.onTick(runner) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/helpers/EntityHelpers.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers 2 | 3 | import com.mineinabyss.geary.datatypes.ComponentId 4 | import com.mineinabyss.geary.datatypes.ENTITY_MASK 5 | import com.mineinabyss.geary.datatypes.Entity 6 | import com.mineinabyss.geary.datatypes.EntityId 7 | import com.mineinabyss.geary.modules.Geary 8 | 9 | const val NO_COMPONENT: ComponentId = 0uL 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/action.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Action 3 | about: Does one thing once. Can be defined in configs, ex. run a list of actions when an entity gets hit. 4 | title: '' 5 | labels: type:action 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Purpose 11 | *What does triggering this action do?* 12 | 13 | ## Data 14 | - Describe a list of properties that should be configurable 15 | - ex. potion effect duration 16 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/accessors/FamilyMatching.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.accessors 2 | 3 | import com.mineinabyss.geary.datatypes.family.Family 4 | 5 | /** 6 | * Used for accessors that require a family to be matched against to work 7 | * (ex a component accessor needs the component present on the entity.) 8 | */ 9 | interface FamilyMatching { 10 | val family: Family.Selector? 11 | } 12 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/event_binds/EventBind.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.event_binds 2 | 3 | import com.mineinabyss.geary.actions.Tasks 4 | import com.mineinabyss.geary.serialization.serializers.SerializableComponentId 5 | 6 | class EventBind( 7 | val event: SerializableComponentId, 8 | val involving: List = listOf(), 9 | val actionGroup: Tasks, 10 | ) 11 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/annotations/optin/UnsafeAccessors.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.annotations.optin 2 | 3 | @RequiresOptIn( 4 | level = RequiresOptIn.Level.ERROR, 5 | message = "Reading and writing entity data without an accessor reduces speed and does not enforce null safety for accessors." + 6 | " Be careful when manually removing components used by other accessors." 7 | ) 8 | annotation class UnsafeAccessors 9 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/System.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems 2 | 3 | import com.mineinabyss.geary.systems.query.CachedQuery 4 | import com.mineinabyss.geary.systems.query.Query 5 | import kotlin.time.Duration 6 | 7 | class System @PublishedApi internal constructor( 8 | val name: String, 9 | val query: T, 10 | val onTick: CachedQuery.() -> Unit, 11 | val interval: Duration?, 12 | ) 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/condition.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Condition 3 | about: A check that can be defined in configs. Ex whether a player is sprinting. 4 | title: '' 5 | labels: type:condition 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Purpose 11 | *What does this condition check for?* 12 | 13 | ## Data 14 | - Describe a list of properties that should be configurable 15 | - ex. a radius to check in, or a list of smaller checks that should either be true or false. 16 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/GearyActions.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions 2 | 3 | import com.mineinabyss.geary.actions.event_binds.bindEntityObservers 4 | import com.mineinabyss.geary.actions.event_binds.parsePassive 5 | import com.mineinabyss.geary.addons.dsl.createAddon 6 | 7 | val GearyActions = createAddon("Geary Actions") { 8 | onStart { 9 | bindEntityObservers() 10 | parsePassive() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/configuration/components/ReEmitEvent.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.configuration.components 2 | 3 | import com.mineinabyss.geary.datatypes.ComponentId 4 | import com.mineinabyss.geary.serialization.serializers.SerializableComponentId 5 | 6 | data class ReEmitEvent( 7 | val findByRelationKind: SerializableComponentId, 8 | val dataComponentId: ComponentId, 9 | val data: Any?, 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/formats/Formats.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.formats 2 | 3 | import kotlinx.serialization.cbor.Cbor 4 | 5 | interface Formats { 6 | /** Gets a registered [Format] for a file with extension [ext]. */ 7 | operator fun get(ext: String): Format? 8 | 9 | /** The format to use for encoding binary data (usually not to files) */ 10 | val binaryFormat: Cbor 11 | } 12 | 13 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/TickingEngine.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import kotlin.time.Duration 4 | 5 | abstract class TickingEngine : Engine { 6 | abstract val tickDuration: Duration 7 | 8 | private var started: Boolean = false 9 | 10 | open fun start(): Boolean { 11 | if (!started) { 12 | started = true 13 | return true 14 | } 15 | return false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/dsl/Aliases.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.dsl 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.modules.PolymorphicModuleBuilder 5 | import kotlin.reflect.KClass 6 | 7 | /** The polymorphic builder scope that allows registering subclasses. */ 8 | typealias SerializerRegistry = PolymorphicModuleBuilder.(kClass: KClass, serializer: KSerializer?) -> Unit 9 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/EntityStack.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | class EntityStack( 4 | @PublishedApi 5 | internal val stack: ArrayDeque = ArrayDeque(), 6 | ) { 7 | fun push(entity: EntityId) { 8 | stack.add(entity.toLong()) 9 | } 10 | 11 | inline fun popOrElse(orElse: () -> EntityId): EntityId = 12 | if (stack.isEmpty()) orElse() 13 | else stack.removeFirst().toULong() 14 | } 15 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/Engine.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | 5 | /** 6 | * An engine service for running the Geary ECS. 7 | * 8 | * Its companion object gets a service via Bukkit as its implementation. 9 | */ 10 | interface Engine { 11 | val mainScope: CoroutineScope 12 | 13 | /** Ticks the entire engine. Implementations may call at different speeds. */ 14 | fun tick() 15 | } 16 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/configuration/components/Prefab.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.configuration.components 2 | 3 | import kotlinx.io.files.Path 4 | 5 | /** 6 | * A component applied to prefabs loaded from a file that allows them to be reread. 7 | * 8 | * @param file The file this prefab was loaded from or null if it is a child in a prefab, or created some other way. 9 | */ 10 | class Prefab( 11 | val file: Path? = null 12 | ) 13 | -------------------------------------------------------------------------------- /addons/geary-uuid/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(idofrontLibs.plugins.mia.kotlin.multiplatform.get().pluginId) 3 | id(idofrontLibs.plugins.mia.publication.get().pluginId) 4 | } 5 | 6 | kotlin { 7 | sourceSets { 8 | commonMain { 9 | dependencies { 10 | implementation(project(":geary-core")) 11 | 12 | implementation(libs.atomicfu) 13 | } 14 | kotlin.srcDirs("src") 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/PrefabPath.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | import kotlinx.io.Source 4 | import kotlinx.io.files.Path 5 | 6 | data class PrefabPath( 7 | val namespace: String, 8 | val paths: () -> Sequence = { emptySequence() }, 9 | val sources: () -> Sequence = { emptySequence() }, 10 | ) 11 | 12 | data class PrefabSource( 13 | val source: Source, 14 | val key: PrefabKey, 15 | val formatExt: String, 16 | ) 17 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/modules/TestEngineModule.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.modules 2 | 3 | /** 4 | * An engine module that initializes the engine but does not start it, useful for testing. 5 | * 6 | * No pipeline tasks are run, and the engine won't be scheduled for ticking. 7 | * Engine ticks may still be called manually. 8 | */ 9 | // TODO set beginTickingOnStart = false property 10 | val TestEngineModule get() = ArchetypeEngineModule( 11 | beginTickingOnStart = false 12 | ) 13 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/observers/EventRunner.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers 2 | 3 | import com.mineinabyss.geary.datatypes.ComponentId 4 | import com.mineinabyss.geary.datatypes.EntityId 5 | 6 | interface EventRunner { 7 | fun addObserver(observer: Observer) 8 | 9 | fun removeObserver(observer: Observer) 10 | 11 | fun callEvent( 12 | eventType: ComponentId, 13 | eventData: Any?, 14 | involvedComponent: ComponentId, 15 | entity: EntityId, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/Action.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions 2 | 3 | import kotlinx.serialization.json.Json 4 | 5 | interface Action { 6 | /** Should this action create a copy of [ActionGroupContext] to run or not? */ 7 | val useSubcontext: Boolean get() = true 8 | 9 | fun ActionGroupContext.execute(): Any? 10 | } 11 | 12 | fun Action.execute(context: ActionGroupContext) = context.execute() 13 | fun main() { 14 | Json { 15 | prettyPrint = true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/expressions/FunctionExpressionWithInput.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.expressions 2 | 3 | import com.mineinabyss.geary.actions.ActionGroupContext 4 | 5 | class FunctionExpressionWithInput( 6 | val ref: Expression<*>, 7 | val expr: FunctionExpression, 8 | ) : Expression { 9 | override fun evaluate(context: ActionGroupContext): O { 10 | val input = ref.evaluate(context) as I 11 | return expr.map(input, context) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /addons/geary-autoscan/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(idofrontLibs.plugins.mia.kotlin.jvm.get().pluginId) 3 | id(idofrontLibs.plugins.mia.publication.get().pluginId) 4 | alias(idofrontLibs.plugins.kotlinx.serialization) 5 | } 6 | 7 | dependencies { 8 | implementation(project(":geary-core")) 9 | implementation(project(":geary-serialization")) 10 | 11 | implementation(idofrontLibs.reflections) 12 | implementation(idofrontLibs.kotlin.reflect) 13 | } 14 | 15 | sourceSets.main { 16 | kotlin.srcDirs("src") 17 | } 18 | -------------------------------------------------------------------------------- /geary-test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(idofrontLibs.plugins.mia.kotlin.jvm.get().pluginId) 3 | id(idofrontLibs.plugins.mia.publication.get().pluginId) 4 | alias(idofrontLibs.plugins.kotlinx.serialization) 5 | } 6 | 7 | dependencies { 8 | implementation(project(":geary-core")) 9 | implementation(kotlin("test")) 10 | implementation(idofrontLibs.kotlinx.coroutines.test) 11 | implementation(libs.koin.test) 12 | compileOnly(idofrontLibs.junit.jupiter) 13 | } 14 | 15 | sourceSets.main { 16 | kotlin.srcDirs("src") 17 | } 18 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/accessors/type/MappedAccessor.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.accessors.type 2 | 3 | import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor 4 | import com.mineinabyss.geary.systems.query.Query 5 | 6 | class MappedAccessor( 7 | override val originalAccessor: ReadOnlyAccessor, 8 | val mapping: (T) -> U, 9 | ) : ReadOnlyAccessor { 10 | override fun get(query: Query): U { 11 | val value = originalAccessor.get(query) 12 | return mapping(value) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/publish-packages.yml: -------------------------------------------------------------------------------- 1 | name: Publish Packages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | paths-ignore: 9 | - '**.md' 10 | - docs 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - uses: MineInAbyss/publish-action@v3 22 | with: 23 | maven-username: ${{ secrets.MAVEN_PUBLISH_USERNAME }} 24 | maven-password: ${{ secrets.MAVEN_PUBLISH_PASSWORD }} 25 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/unpacking/Systems.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.unpacking 2 | 3 | import com.mineinabyss.geary.benchmarks.helpers.* 4 | import com.mineinabyss.geary.modules.Geary 5 | import com.mineinabyss.geary.systems.query.GearyQuery 6 | import com.mineinabyss.geary.systems.query.query 7 | 8 | fun Geary.systemOf1() = cache(query()) 9 | fun Geary.systemOf1OrNull() = cache(query()) 10 | fun Geary.systemOf2() = cache(query()) 11 | fun Geary.systemOf6() = cache(query()) 12 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/datatypes/EntityTypeTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | import com.mineinabyss.geary.test.GearyTest 4 | import io.kotest.matchers.shouldBe 5 | import org.junit.jupiter.api.Test 6 | 7 | internal class EntityTypeTest : GearyTest() { 8 | @Test 9 | fun typeSorting() { 10 | val type = EntityType(listOf(3u, 1u, 2u)) 11 | type.inner shouldBe ulongArrayOf(1u, 2u, 3u) 12 | (type + 4u).inner shouldBe ulongArrayOf(1u, 2u, 3u, 4u) 13 | (type + 1u).inner shouldBe ulongArrayOf(1u, 2u, 3u) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/helpers/tests/GearyTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers.tests 2 | 3 | import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider 4 | import com.mineinabyss.geary.modules.Geary 5 | import com.mineinabyss.geary.modules.TestEngineModule 6 | import com.mineinabyss.geary.modules.geary 7 | import com.mineinabyss.geary.modules.get 8 | import kotlinx.coroutines.Deferred 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.async 11 | import kotlinx.coroutines.withContext 12 | import org.junit.jupiter.api.AfterAll 13 | import org.koin.core.KoinApplication 14 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/configuration/components/RelationOnPrefab.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.configuration.components 2 | 3 | import com.mineinabyss.geary.datatypes.Component 4 | import kotlinx.serialization.Polymorphic 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * A component that will add a relation to this entity with a [target], [data] pair. 10 | */ 11 | @Serializable 12 | @SerialName("geary:relation") 13 | data class RelationOnPrefab( 14 | val target: String, 15 | val data: @Polymorphic Component, 16 | ) 17 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/accessors/RelationWithData.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.accessors 2 | 3 | import com.mineinabyss.geary.datatypes.Component 4 | import com.mineinabyss.geary.datatypes.EntityId 5 | import com.mineinabyss.geary.datatypes.Relation 6 | 7 | /** 8 | * Helper class for getting a compact overview of data stored in a relation. 9 | */ 10 | data class RelationWithData( 11 | val data: K, 12 | val targetData: T, 13 | val relation: Relation, 14 | ) { 15 | val kind: EntityId = relation.kind 16 | val target: EntityId = relation.target 17 | } 18 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/helpers/Component.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.helpers 2 | 3 | import com.mineinabyss.geary.datatypes.ComponentId 4 | import com.mineinabyss.geary.helpers.componentId 5 | import com.mineinabyss.geary.modules.Geary 6 | import com.mineinabyss.geary.serialization.SerializableComponents 7 | 8 | /** 9 | * Gets the id of a component by its serial name. 10 | * Throws an error if the component name does not exist. 11 | */ 12 | fun Geary.componentId(serialName: String): ComponentId = 13 | componentId(getAddon(SerializableComponents).serializers.getClassFor(serialName)) 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/system-behaviour.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: System/Behaviour 3 | about: A constantly running feature (ex giving mobs a walking animation when they 4 | move) 5 | title: '' 6 | labels: type:behavior 7 | assignees: '' 8 | --- 9 | 10 | ## Purpose 11 | *What does this behaviour actually do?* 12 | 13 | ## Match 14 | *Describe what kinds of entities this should run on (other components needed)* 15 | - ex. Must be an Item 16 | 17 | If you wish to match new components, follow this template: 18 | 19 | ### New component name 20 | - Describe a list of properties that should be configurable 21 | - ex. the stationary/walking models to use for animating mobs 22 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/maps/TypeMap.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes.maps 2 | 3 | import com.mineinabyss.geary.datatypes.Entity 4 | import com.mineinabyss.geary.datatypes.EntityId 5 | import com.mineinabyss.geary.engine.archetypes.Archetype 6 | 7 | interface TypeMap { 8 | /** Updates the record of a given entity */ 9 | operator fun set(entity: EntityId, archetype: Archetype, row: Int) 10 | 11 | /** Removes a record associated with an entity. */ 12 | fun remove(entity: EntityId) 13 | 14 | /** Checks if an entity has a record associated with it. */ 15 | operator fun contains(entity: EntityId): Boolean 16 | } 17 | -------------------------------------------------------------------------------- /addons/geary-autoscan/src/com/mineinabyss/geary/autoscan/AutoscanAnnotations.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.autoscan 2 | 3 | import com.mineinabyss.geary.observers.Observer 4 | import com.mineinabyss.geary.systems.GearySystem 5 | import com.mineinabyss.geary.systems.query.GearyQuery 6 | 7 | /** 8 | * Excludes this class from having its serializer automatically registered for component serialization 9 | * with the AutoScanner. 10 | */ 11 | annotation class ExcludeAutoScan 12 | 13 | /** 14 | * Indicates this [GearySystem], such as [RepeatingSystem], [Observer], or [GearyQuery] be registered automatically 15 | * on startup by the AutoScanner. 16 | */ 17 | annotation class AutoScan 18 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/configuration/components/ChildOnPrefab.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.configuration.components 2 | 3 | import com.mineinabyss.geary.datatypes.Component 4 | import kotlinx.serialization.Polymorphic 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | import kotlin.jvm.JvmInline 8 | 9 | /** 10 | * > geary:child 11 | * 12 | * A component that adds a child entity to this entity from the components defined in [components] 13 | */ 14 | @JvmInline 15 | @Serializable 16 | @SerialName("geary:child") 17 | value class ChildOnPrefab( 18 | val components: List<@Polymorphic Component> 19 | ) 20 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/modules/ArchetypeEngineInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.modules 2 | 3 | import com.mineinabyss.geary.engine.Pipeline 4 | import com.mineinabyss.geary.engine.archetypes.ArchetypeEngine 5 | import com.mineinabyss.geary.engine.archetypes.ComponentAsEntityProvider 6 | 7 | class ArchetypeEngineInitializer( 8 | val beginTickingOnStart: Boolean, 9 | private val pipeline: Pipeline, 10 | private val engine: ArchetypeEngine, 11 | ) : EngineInitializer { 12 | override fun init() { 13 | } 14 | 15 | override fun start() { 16 | pipeline.runStartupTasks() 17 | if (beginTickingOnStart) engine.start() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quickstart](/quickstart) 3 | 4 | # Guide 5 | - [Setup](/guide/setup) 6 | - [Addons](/guide/addons) 7 | - [Entities](/guide/entities) 8 | - [Designing Components](/guide/designing-components) 9 | - [Relations](/guide/relations) 10 | 11 | # Backend 12 | 13 | - [Backend](/backend) 14 | - [Resources](/backend/resources) 15 | - Core 16 | - [Overview](/backend/core) 17 | - [Archetype Graph](/backend/core/archetype-graph) 18 | - [Querying Archetypes](/backend/core/querying-archetypes) 19 | - [Changing Data](/backend/core/changing-data) 20 | - [Events](/backend/core/events) 21 | 22 | - Future plans 23 | - [Memory structure](/backend/future-plans/memory-structure) 24 | -------------------------------------------------------------------------------- /addons/geary-actions/README.md: -------------------------------------------------------------------------------- 1 | # Geary actions 2 | 3 | This module implements a config system for programming custom actions. It's similar to Ansible in structure and requires YAML deserialization, thus is JVM-only for now. 4 | 5 | ## Terminology 6 | 7 | Terms used by this module are loosely based off ansible, here are some definitions: 8 | 9 | - **Task**: One action or condition, along with extra parameters like `loop`, `onFail`, etc... 10 | - **Action**: A single action to perform, ex. spawning a particle, modifying an entity in some way, etc... 11 | - **Condition**: Like an action, but always returns either true or false, conditions can be used with the `when` option in tasks to prevent them from running upon failure. 12 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/helpers/GearyEntityWithOperatorTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers 2 | 3 | import com.mineinabyss.geary.test.GearyTest 4 | import io.kotest.matchers.shouldBe 5 | import org.junit.jupiter.api.Test 6 | 7 | internal class GearyEntityWithOperatorTest : GearyTest() { 8 | @Test 9 | fun nullable_with_extensions() { 10 | val entity = entity { 11 | set("") 12 | set(1) 13 | } 14 | 15 | (entity.with { _: String, _: Int -> true } ?: false) shouldBe true 16 | (entity.with { _: String, _: Double -> true } ?: false) shouldBe false 17 | (entity.with { _: String, _: Double? -> true } ?: false) shouldBe true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/BitSet.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | /** 4 | * Cross-platform interface for a bitset. 5 | */ 6 | expect class BitSet() { 7 | // public val length: Int 8 | fun isEmpty(): Boolean 9 | operator fun get(index: Int): Boolean 10 | fun set(index: Int) 11 | fun set(from: Int, to: Int) 12 | fun clear(index: Int) 13 | fun flip(index: Int) 14 | fun and(other: BitSet) 15 | fun andNot(other: BitSet) 16 | fun or(other: BitSet) 17 | fun xor(other: BitSet) 18 | fun clear() 19 | val cardinality: Int 20 | 21 | inline fun forEachBit(crossinline loop: (Int) -> Unit) 22 | 23 | fun copy(): BitSet 24 | 25 | } 26 | 27 | fun bitsOf(): BitSet = BitSet() 28 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/EntityInfoReader.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.datatypes.Entity 4 | 5 | /** 6 | * Allow addons to provide extra information about an entity. 7 | * 8 | * Used when calling [Entity.toString] 9 | */ 10 | class EntityInfoReader { 11 | private val lines = mutableMapOf String?>() 12 | 13 | fun addInfoLine(name: String, eval: (Entity) -> String?) { 14 | lines[name] = eval 15 | } 16 | 17 | fun readEntityInfo(entity: Entity): String { 18 | return lines.mapNotNull { 19 | val value = it.value(entity) ?: return@mapNotNull null 20 | "${it.key}=$value" 21 | }.joinToString(", ") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/addons/GearyPhase.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.addons 2 | 3 | /** 4 | * Different phases of Geary's startup process. 5 | * 6 | * They are executed top to bottom. 7 | */ 8 | enum class GearyPhase { 9 | /** All addons have been installed and configured. */ 10 | ADDONS_CONFIGURED, 11 | 12 | /** Initialize new components and any information related to them. */ 13 | INIT_COMPONENTS, 14 | 15 | /** Initialize any systems before entities start being added to the world. */ 16 | INIT_SYSTEMS, 17 | 18 | /** Create any entities that should exist before the engine starts running. */ 19 | INIT_ENTITIES, 20 | 21 | /** All previous registration tasks have been completed. */ 22 | ENABLE, 23 | } 24 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/expressions/EntityExpression.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.expressions 2 | 3 | import com.mineinabyss.geary.actions.ActionGroupContext 4 | import com.mineinabyss.geary.datatypes.GearyEntity 5 | import com.mineinabyss.geary.helpers.parent 6 | import kotlinx.serialization.Serializable 7 | 8 | @JvmInline 9 | @Serializable 10 | value class EntityExpression( 11 | val expression: String, 12 | ) : Expression { 13 | override fun evaluate(context: ActionGroupContext): GearyEntity { 14 | return if (expression == "parent") context.entity?.parent ?: error("No parent entity in environment") 15 | else Expression.Variable(expression).evaluate(context) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/formats/SimpleFormats.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.formats 2 | 3 | import com.mineinabyss.geary.datatypes.Component 4 | import kotlinx.serialization.cbor.Cbor 5 | 6 | /** 7 | * A singleton for accessing different serialization formats with all the registered serializers for [Component]s 8 | * and more. If anything should be serialized within the ECS, it should be going through one of these serializers. 9 | * 10 | * Will likely be converted into a service eventually. 11 | */ 12 | class SimpleFormats( 13 | override val binaryFormat: Cbor, 14 | private val formats: Map, 15 | ) : Formats { 16 | 17 | override operator fun get(ext: String): Format? = formats[ext] 18 | 19 | } 20 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/observers/Observer.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers 2 | 3 | import com.mineinabyss.geary.datatypes.ComponentId 4 | import com.mineinabyss.geary.datatypes.Entity 5 | import com.mineinabyss.geary.datatypes.EntityId 6 | import com.mineinabyss.geary.datatypes.EntityType 7 | import com.mineinabyss.geary.datatypes.family.Family 8 | import com.mineinabyss.geary.systems.query.Query 9 | 10 | data class Observer( 11 | val queries: List, 12 | val family: Family, 13 | val involvedComponents: EntityType, 14 | val listenToEvents: EntityType, 15 | val mustHoldData: Boolean, 16 | val handle: ObserverHandle, 17 | ) 18 | 19 | fun interface ObserverHandle { 20 | fun run(entity: EntityId, data: Any?, involvedComponent: ComponentId?) 21 | } 22 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/helpers/ComponentHelpers.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers 2 | 3 | import com.mineinabyss.geary.datatypes.* 4 | import com.mineinabyss.geary.modules.Geary 5 | import kotlin.reflect.KClass 6 | 7 | fun EntityId.readableString(world: Geary): String = buildString { 8 | val id = this@readableString 9 | if (id.hasRole(RELATION)) { 10 | append(id.toRelation().toString()) 11 | return@buildString 12 | } 13 | if (id.hasRole(RELATION)) append("R") else append('-') 14 | if (id.hasRole(HOLDS_DATA)) append("D") else append('-') 15 | append(" ") 16 | val componentName = (world.getComponentInfo(id)?.kClass as? KClass<*>)?.simpleName 17 | if (componentName == null) append(id and ENTITY_MASK) 18 | else append(componentName) 19 | } 20 | -------------------------------------------------------------------------------- /addons/geary-serialization/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(idofrontLibs.plugins.mia.kotlin.multiplatform.get().pluginId) 3 | id(idofrontLibs.plugins.mia.publication.get().pluginId) 4 | alias(idofrontLibs.plugins.kotlinx.serialization) 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | commonMain { 10 | dependencies { 11 | implementation(project(":geary-core")) 12 | 13 | api(idofrontLibs.kotlinx.serialization.cbor) 14 | api(idofrontLibs.kotlinx.serialization.json) 15 | api(idofrontLibs.kotlinx.serialization.kaml) 16 | api(idofrontLibs.kotlinx.io) 17 | } 18 | kotlin.srcDirs("src") 19 | } 20 | 21 | jvmMain { 22 | kotlin.srcDirs("src@jvm") 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/gradle-ci.yml: -------------------------------------------------------------------------------- 1 | name: Java CI with Gradle 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '**.md' 9 | - docs 10 | pull_request: 11 | 12 | concurrency: 13 | cancel-in-progress: true 14 | group: ci-${{ github.ref }} 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Set up JDK 23 | uses: actions/setup-java@v3 24 | with: 25 | distribution: temurin 26 | java-version: 21 27 | 28 | - name: Setup Gradle 29 | uses: gradle/actions/setup-gradle@v3 30 | 31 | - name: Grant execute permission for gradlew 32 | shell: bash 33 | run: chmod +x gradlew 34 | 35 | - name: Build 36 | run: ./gradlew build 37 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/actions/EmitEventAction.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.actions 2 | 3 | import com.mineinabyss.geary.actions.Action 4 | import com.mineinabyss.geary.actions.ActionGroupContext 5 | import com.mineinabyss.geary.datatypes.ComponentId 6 | import com.mineinabyss.geary.helpers.componentId 7 | import com.mineinabyss.geary.modules.Geary 8 | 9 | class EmitEventAction( 10 | val eventId: ComponentId, 11 | val data: Any?, 12 | ) : Action { 13 | override fun ActionGroupContext.execute() { 14 | entity?.emit(event = eventId, data = data) 15 | } 16 | 17 | companion object { 18 | fun from(world: Geary, data: Any) = EmitEventAction(world.componentId(data::class), data) 19 | 20 | fun wrapIfNotAction(world: Geary, data: Any) = if (data is Action) data else from(world, data) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/configuration/components/InheritPrefabs.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.configuration.components 2 | 3 | import com.mineinabyss.geary.prefabs.PrefabKey 4 | import com.mineinabyss.geary.serialization.serializers.InnerSerializer 5 | import kotlinx.serialization.Serializable 6 | import kotlinx.serialization.builtins.SetSerializer 7 | 8 | /** 9 | * > geary:inherit 10 | * 11 | * Will make this entity an instance of all entities defined in [from] 12 | */ 13 | @Serializable(with = InheritPrefabs.Serializer::class) 14 | class InheritPrefabs( 15 | val from: Set 16 | ) { 17 | class Serializer : InnerSerializer, InheritPrefabs>( 18 | "geary:inherit", 19 | SetSerializer(PrefabKey.serializer()), 20 | { InheritPrefabs(it) }, 21 | { it.from } 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/Pipeline.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.addons.GearyPhase 4 | import com.mineinabyss.geary.systems.System 5 | import com.mineinabyss.geary.systems.TrackedSystem 6 | import com.mineinabyss.geary.systems.query.Query 7 | 8 | interface Pipeline { 9 | fun runOnOrAfter(phase: GearyPhase, block: () -> Unit) 10 | fun onSystemAdd(run: (System<*>) -> Unit) 11 | fun runStartupTasks() 12 | 13 | /** Adds a [system] to the engine, which will be ticked appropriately by the engine. */ 14 | fun addSystem(system: System): TrackedSystem<*> 15 | 16 | fun addSystems(vararg systems: System<*>) 17 | 18 | /** Gets all registered systems in the order they should be executed during an engine tick. */ 19 | fun getRepeatingInExecutionOrder(): Iterable> 20 | } 21 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/components/ReservedComponents.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.components 2 | 3 | import com.mineinabyss.geary.components.relations.ChildOf 4 | import com.mineinabyss.geary.datatypes.entityTypeOf 5 | 6 | /** 7 | * Entity id references that are used internally [EntityProvider] should ensure to sequentially create entities to 8 | * account for these. As a result, these ids should be sequential, starting at zero. 9 | * 10 | * Keeping these as const vals helps improve performance in tight loops that might be referencing these often. 11 | */ 12 | object ReservedComponents { 13 | const val COMPONENT_INFO = 0uL 14 | const val ANY = 1uL 15 | const val CHILD_OF = 2uL 16 | 17 | val reservedComponents = mapOf( 18 | ComponentInfo::class to COMPONENT_INFO, 19 | Any::class to ANY, 20 | ChildOf::class to CHILD_OF, 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/GearyAliases.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | typealias GearyEntityType = EntityType 4 | typealias GearyRelation = Relation 5 | 6 | /** 7 | * GearyComponents aren't an interface because we like the option to be able to add `Any` class as a component to an 8 | * entity. However, for clarity we create this typealias. 9 | * 10 | * Since [Any] is not inherently serializable like an interface, when expecting a [Component] to be serializable, 11 | * use the [Polymorphic] annotation. 12 | */ 13 | typealias Component = Any 14 | 15 | /** Type alias for entity IDs. */ 16 | typealias EntityId = ULong 17 | 18 | /** Type alias for component IDs. Is the same as [EntityId]. */ 19 | typealias ComponentId = EntityId 20 | 21 | typealias GearyComponent = Component 22 | typealias GearyEntityId = EntityId 23 | typealias GearyComponentId = ComponentId 24 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/engine/GearyEngineTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.helpers.entity 4 | import com.mineinabyss.geary.test.GearyTest 5 | import io.kotest.matchers.shouldBe 6 | import org.junit.jupiter.api.Nested 7 | import org.junit.jupiter.api.Test 8 | 9 | internal class GearyEngineTest : GearyTest() { 10 | @Nested 11 | inner class EntityRemoval { 12 | @Test 13 | fun `entity removal and reuse`() { 14 | val offset = entity().id + 1uL 15 | repeat(100) { 16 | entity() 17 | } 18 | 19 | // We filled up ids 0..9, so next should be at 10 20 | entity().id shouldBe offset + 100uL 21 | 22 | (0 until 100).forEach { 23 | entityRemoveProvider.remove((offset + it.toULong())) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/observers/QueryAsMapTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers 2 | 3 | import com.mineinabyss.geary.helpers.entity 4 | import com.mineinabyss.geary.test.GearyTest 5 | import com.mineinabyss.geary.observers.queries.cacheAssociatedBy 6 | import com.mineinabyss.geary.systems.query.query 7 | import io.kotest.matchers.shouldBe 8 | import org.junit.jupiter.api.Test 9 | 10 | class QueryAsMapTest : GearyTest() { 11 | @Test 12 | fun `should correctly track entity in map`() { 13 | val map = cacheAssociatedBy(query()) { (string) -> string } 14 | 15 | entity { 16 | set("Hello world") 17 | map["Hello world"] shouldBe this 18 | remove() 19 | map["Hello world"] shouldBe null 20 | set("Hello world") 21 | removeEntity() 22 | map["Hello world"] shouldBe null 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/backend/resources.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | This page includes resources I used to learn about ECS design. 4 | 5 | - [ECS FAQ](https://github.com/SanderMertens/ecs-faq) 6 | An overview of everything ECS and people's different approaches. 7 | - [Building an ECS #1: Types, Hierarchies and Prefabs](https://ajmmertens.medium.com/building-an-ecs-1-types-hierarchies-and-prefabs-9f07666a1e9d) 8 | A guide about the design of Flecs by its creator. 9 | - [ECS back and forth](https://skypjack.github.io/2019-02-14-ecs-baf-part-1/) 10 | An overview of ECS and many performance tricks used by the creator of EnTT. 11 | - The Flecs Discord server 12 | It has channels for making your own ECS. You can find it on [their homepage](https://www.flecs.dev/flecs/). 13 | - [Overwatch gameplay architecture and netcode](https://www.youtube.com/watch?v=W3aieHjyNvw) 14 | A talk that goes into *what* you can do with an ECS and some design decisions behind the one Overwatch uses. 15 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/Tasks.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions 2 | 3 | import com.mineinabyss.geary.serialization.serializers.InnerSerializer 4 | import kotlinx.serialization.Serializable 5 | import kotlinx.serialization.builtins.ListSerializer 6 | 7 | @Serializable(with = Tasks.Serializer::class) 8 | class Tasks( 9 | val tasks: List<@Serializable(with = TaskActionByNameSerializer::class) Task>, 10 | ) : Action { 11 | override fun ActionGroupContext.execute() { 12 | tasks.forEach { task -> 13 | try { 14 | task.execute(this) 15 | } catch (_: ActionsCancelledException) { 16 | return 17 | } 18 | } 19 | } 20 | 21 | object Serializer : InnerSerializer>, Tasks>( 22 | "geary:run", 23 | ListSerializer(TaskActionByNameSerializer), 24 | { Tasks(it) }, 25 | { it.tasks } 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/observers/RemoveObserverTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers 2 | 3 | import com.mineinabyss.geary.helpers.entity 4 | import com.mineinabyss.geary.modules.observe 5 | import com.mineinabyss.geary.observers.events.OnSet 6 | import com.mineinabyss.geary.systems.query.query 7 | import com.mineinabyss.geary.test.GearyTest 8 | import io.kotest.matchers.shouldBe 9 | import kotlin.test.Test 10 | 11 | class RemoveObserverTest : GearyTest() { 12 | @Test 13 | fun `should stop observing events after removed`() { 14 | val called = mutableListOf() 15 | val observer = observe().exec(query()) { (data) -> 16 | called += data 17 | } 18 | 19 | val entity = entity { 20 | set(1) 21 | set(2) 22 | } 23 | 24 | eventRunner.removeObserver(observer) 25 | 26 | entity.set(3) 27 | 28 | called shouldBe listOf(1, 2) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/serializers/PrefabKeySerializer.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.serializers 2 | 3 | import com.mineinabyss.geary.prefabs.PrefabKey 4 | import kotlinx.serialization.KSerializer 5 | import kotlinx.serialization.descriptors.PrimitiveKind 6 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 7 | import kotlinx.serialization.descriptors.SerialDescriptor 8 | import kotlinx.serialization.encoding.Decoder 9 | import kotlinx.serialization.encoding.Encoder 10 | 11 | object PrefabKeySerializer : KSerializer { 12 | override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("geary:prefab_key", PrimitiveKind.STRING) 13 | 14 | override fun deserialize(decoder: Decoder): PrefabKey { 15 | return PrefabKey.of(decoder.decodeString()) 16 | } 17 | 18 | override fun serialize(encoder: Encoder, value: PrefabKey) { 19 | encoder.encodeString(value.toString()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/helpers/SerializationHelpers.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.helpers 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.descriptors.PrimitiveKind 5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | 8 | /** 9 | * A wrapper around [KSerializer] that only overrides the [descriptor]. 10 | * Not technically needed but doing this just in case. 11 | */ 12 | class SerializerWrapper(override val descriptor: SerialDescriptor, wrapped: KSerializer) : 13 | KSerializer by wrapped 14 | 15 | fun KSerializer.withSerialName(name: String): SerializerWrapper { 16 | val kind = descriptor.kind 17 | return if (kind is PrimitiveKind) SerializerWrapper(PrimitiveSerialDescriptor(name, kind), this) 18 | else SerializerWrapper(SerialDescriptor(name, this.descriptor), this) 19 | } 20 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/dsl/builders/ComponentSerializersBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.dsl.builders 2 | 3 | import com.mineinabyss.geary.datatypes.Component 4 | import com.mineinabyss.geary.serialization.ComponentSerializers 5 | import com.mineinabyss.geary.serialization.SerializersByMap 6 | import kotlinx.serialization.modules.EmptySerializersModule 7 | import kotlinx.serialization.modules.SerializersModule 8 | import kotlinx.serialization.modules.overwriteWith 9 | import kotlin.reflect.KClass 10 | 11 | class ComponentSerializersBuilder { 12 | val modules = mutableListOf() 13 | val serialNameToClass = mutableMapOf>() 14 | 15 | fun build(): ComponentSerializers = SerializersByMap( 16 | modules.fold(EmptySerializersModule()) { acc, module -> 17 | acc.overwriteWith(module) 18 | }, 19 | serialNameToClass.toMap() 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/dsl/WithCommonComponentNames.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.dsl 2 | 3 | import com.mineinabyss.geary.components.relations.ChildOf 4 | import com.mineinabyss.geary.components.relations.InstanceOf 5 | import com.mineinabyss.geary.observers.events.* 6 | import com.mineinabyss.geary.serialization.SerializableComponentsBuilder 7 | 8 | fun SerializableComponentsBuilder.withCommonComponentNames() { 9 | namedComponent("geary:on_add") 10 | namedComponent("geary:on_set") 11 | namedComponent("geary:on_first_set") 12 | namedComponent("geary:on_remove") 13 | namedComponent("geary:on_update") 14 | namedComponent("geary:on_entity_removed") 15 | namedComponent("geary:on_extend") 16 | namedComponent("geary:child_of") 17 | namedComponent("geary:instance_of") 18 | namedComponent("geary:any") 19 | } 20 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/ComponentProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.datatypes.ComponentId 4 | import com.mineinabyss.geary.datatypes.HOLDS_DATA 5 | import com.mineinabyss.geary.datatypes.NO_ROLE 6 | import com.mineinabyss.geary.datatypes.withRole 7 | import kotlin.reflect.KClassifier 8 | import kotlin.reflect.typeOf 9 | 10 | interface ComponentProvider { 11 | /** 12 | * Given a component's [kClass], returns its [ComponentId], or registers an entity 13 | * with a [ComponentInfo] that will represent this [kClass]'s component type. 14 | */ 15 | fun getOrRegisterComponentIdForClass(kClass: KClassifier): ComponentId 16 | } 17 | 18 | inline fun ComponentProvider.id(): ComponentId = 19 | getOrRegisterComponentIdForClass(T::class) 20 | 21 | inline fun ComponentProvider.idWithNullable(): ComponentId = 22 | id().withRole(if (typeOf().isMarkedNullable) NO_ROLE else HOLDS_DATA) 23 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/actions/EvalAction.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.actions 2 | 3 | import com.mineinabyss.geary.actions.Action 4 | import com.mineinabyss.geary.actions.ActionGroupContext 5 | import com.mineinabyss.geary.actions.expressions.Expression 6 | import com.mineinabyss.geary.actions.expressions.InlineExpressionSerializer 7 | import com.mineinabyss.geary.serialization.serializers.InnerSerializer 8 | import kotlinx.serialization.Serializable 9 | 10 | @Serializable(with = EvalAction.Serializer::class) 11 | class EvalAction( 12 | val expression: Expression<*>, 13 | ) : Action { 14 | override fun ActionGroupContext.execute() = 15 | expression.evaluate(this) 16 | 17 | object Serializer : InnerSerializer, EvalAction>( 18 | serialName = "geary:eval", 19 | inner = InlineExpressionSerializer, 20 | inverseTransform = { it.expression }, 21 | transform = { EvalAction(it) } 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /addons/geary-autoscan/src/com/mineinabyss/geary/autoscan/AutoScanner.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.autoscan 2 | 3 | import co.touchlab.kermit.Severity 4 | import com.mineinabyss.geary.addons.dsl.createAddon 5 | import kotlin.reflect.KClass 6 | import kotlin.reflect.KFunction 7 | 8 | val AutoScanAddon = createAddon("Auto Scan", { AutoScanner() }) { 9 | systems { 10 | configuration.scannedSystems.asSequence() 11 | .onEach { it.call(geary) } 12 | .map { it.name } 13 | .let { 14 | if (geary.logger.config.minSeverity <= Severity.Verbose) 15 | geary.logger.i("Autoscan loaded singleton systems: ${it.joinToString()}") 16 | else geary.logger.i("Autoscan loaded ${it.count()} singleton systems") 17 | } 18 | } 19 | } 20 | 21 | class AutoScanner( 22 | val scannedComponents: MutableSet> = mutableSetOf(), 23 | val scannedSystems: MutableSet> = mutableSetOf(), 24 | ) 25 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes.maps 2 | 3 | import co.touchlab.stately.concurrency.Synchronizable 4 | import co.touchlab.stately.concurrency.synchronize 5 | import com.mineinabyss.geary.datatypes.Entity 6 | import com.mineinabyss.geary.datatypes.EntityId 7 | import com.mineinabyss.geary.engine.archetypes.Archetype 8 | 9 | class SynchronizedArrayTypeMap : ArrayTypeMap() { 10 | private val lock = Synchronizable() 11 | 12 | override fun getArchAndRow(entity: EntityId): ULong { 13 | return lock.synchronize { super.getArchAndRow(entity) } 14 | } 15 | 16 | override fun set(entity: EntityId, archetype: Archetype, row: Int) { 17 | lock.synchronize { super.set(entity, archetype, row) } 18 | } 19 | 20 | override fun remove(entity: EntityId) = lock.synchronize { super.remove(entity) } 21 | override fun contains(entity: EntityId): Boolean = lock.synchronize { super.contains(entity) } 22 | } 23 | -------------------------------------------------------------------------------- /addons/geary-actions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(idofrontLibs.plugins.mia.kotlin.jvm.get().pluginId) 3 | id(idofrontLibs.plugins.mia.publication.get().pluginId) 4 | id(idofrontLibs.plugins.mia.testing.get().pluginId) 5 | alias(idofrontLibs.plugins.kotlinx.serialization) 6 | } 7 | 8 | dependencies { 9 | implementation(project(":geary-core")) 10 | implementation(project(":geary-serialization")) 11 | implementation(idofrontLibs.kotlinx.serialization.kaml) 12 | 13 | testImplementation(project(":geary-test")) 14 | testImplementation(kotlin("test")) 15 | testImplementation(idofrontLibs.kotlinx.coroutines.test) 16 | testImplementation(idofrontLibs.kotest.assertions) 17 | testImplementation(idofrontLibs.kotest.property) 18 | testImplementation(project(":geary-core")) 19 | testImplementation(project(":geary-serialization")) 20 | } 21 | 22 | sourceSets.main { 23 | kotlin.srcDirs("src") 24 | } 25 | 26 | sourceSets.test { 27 | kotlin.srcDirs("test") 28 | } 29 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/accessors/Accessor.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.accessors 2 | 3 | import com.mineinabyss.geary.systems.query.Query 4 | import kotlin.properties.ReadOnlyProperty 5 | import kotlin.properties.ReadWriteProperty 6 | import kotlin.reflect.KProperty 7 | 8 | interface Accessor { 9 | val originalAccessor: Accessor? 10 | } 11 | 12 | interface ReadOnlyAccessor : Accessor, ReadOnlyProperty { 13 | fun get(query: Query): T 14 | 15 | 16 | override fun getValue(thisRef: Query, property: KProperty<*>): T { 17 | return get(thisRef) 18 | } 19 | } 20 | 21 | interface ReadWriteAccessor : ReadOnlyAccessor, ReadWriteProperty { 22 | fun set(query: Query, value: T) 23 | 24 | override fun getValue(thisRef: Query, property: KProperty<*>): T { 25 | return get(thisRef) 26 | } 27 | 28 | override fun setValue(thisRef: Query, property: KProperty<*>, value: T) { 29 | return set(thisRef, value) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/builders/DeferredSystemBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.builders 2 | 3 | import com.mineinabyss.geary.datatypes.GearyEntity 4 | import com.mineinabyss.geary.systems.System 5 | import com.mineinabyss.geary.systems.TrackedSystem 6 | import com.mineinabyss.geary.systems.query.CachedQuery 7 | import com.mineinabyss.geary.systems.query.Query 8 | import com.mineinabyss.geary.systems.query.execOnFinish 9 | 10 | class DeferredSystemBuilder( 11 | val systemBuilder: SystemBuilder, 12 | val mapping: CachedQuery.() -> List> 13 | ) { 14 | inline fun onFinish(crossinline run: (data: R, entity: GearyEntity) -> Unit): TrackedSystem<*> { 15 | val onTick: CachedQuery.() -> Unit = { 16 | mapping().execOnFinish(run) 17 | } 18 | val system = System(systemBuilder.name, systemBuilder.query, onTick, systemBuilder.interval) 19 | return systemBuilder.pipeline.addSystem(system) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/queries/accessors/AccessorDataModificationTests.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.queries.accessors 2 | 3 | import com.mineinabyss.geary.helpers.Comp1 4 | import com.mineinabyss.geary.helpers.entity 5 | import com.mineinabyss.geary.test.GearyTest 6 | import com.mineinabyss.geary.systems.query.Query 7 | import io.kotest.matchers.shouldBe 8 | import kotlin.test.Test 9 | 10 | class AccessorDataModificationTests : GearyTest() { 11 | private fun registerQuery() = cache(object : Query(this) { 12 | var data by get() 13 | }) 14 | 15 | @Test 16 | fun `should allow data modify via accessor`() { 17 | resetEngine() 18 | entity { 19 | set(Comp1(1)) 20 | } 21 | 22 | var count = 0 23 | registerQuery().forEach { q -> 24 | q.data shouldBe Comp1(1) 25 | q.data = Comp1(10) 26 | q.data shouldBe Comp1(10) 27 | count++ 28 | } 29 | count shouldBe 1 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/helpers/GearyTypeFamilyHelpersTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers 2 | 3 | import com.mineinabyss.geary.datatypes.EntityType 4 | import com.mineinabyss.geary.datatypes.Relation 5 | import com.mineinabyss.geary.datatypes.family.MutableFamily 6 | import com.mineinabyss.geary.test.GearyTest 7 | import io.kotest.matchers.shouldBe 8 | import org.junit.jupiter.api.Test 9 | 10 | class GearyTypeFamilyHelpersTest : GearyTest() { 11 | @Test 12 | fun containsRelation() { 13 | val type = EntityType(listOf(Relation.of(2uL, 1uL).id, 2uL)) 14 | type.hasRelationTarget(1uL) shouldBe true 15 | type.hasRelationTarget(2uL) shouldBe false 16 | } 17 | 18 | @Test 19 | fun contains() { 20 | val type = entity { setRelation("", 10uL.toGeary()) }.type 21 | MutableFamily.Leaf.KindToAny(componentId(), false).contains(type) shouldBe true 22 | MutableFamily.Leaf.KindToAny(componentId(), true).contains(type) shouldBe false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/unpacking/Unpack1Benchmark.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.unpacking 2 | 3 | import com.mineinabyss.geary.benchmarks.helpers.Comp1 4 | import com.mineinabyss.geary.benchmarks.helpers.GearyBenchmark 5 | import com.mineinabyss.geary.benchmarks.helpers.tenMil 6 | import com.mineinabyss.geary.helpers.entity 7 | import org.openjdk.jmh.annotations.Benchmark 8 | import org.openjdk.jmh.annotations.Scope 9 | import org.openjdk.jmh.annotations.Setup 10 | import org.openjdk.jmh.annotations.State 11 | 12 | @State(Scope.Benchmark) 13 | class Unpack1Benchmark : GearyBenchmark() { 14 | @Setup 15 | fun setUp() { 16 | repeat(tenMil) { 17 | entity { 18 | set(Comp1(1)) 19 | } 20 | } 21 | } 22 | 23 | @Benchmark 24 | fun unpack1of1Comp() { 25 | systemOf1().forEach { (a) -> } 26 | } 27 | 28 | @Benchmark 29 | fun unpack1Nullable() { 30 | systemOf1OrNull().forEach { (a) -> 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/expressions/InlineExpressionSerializer.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.expressions 2 | 3 | import com.mineinabyss.geary.serialization.getWorld 4 | import kotlinx.serialization.KSerializer 5 | import kotlinx.serialization.builtins.serializer 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | import kotlinx.serialization.encoding.Decoder 8 | import kotlinx.serialization.encoding.Encoder 9 | 10 | object InlineExpressionSerializer : KSerializer> { 11 | override val descriptor: SerialDescriptor = String.serializer().descriptor 12 | 13 | override fun deserialize(decoder: Decoder): Expression<*> { 14 | val world = decoder.serializersModule.getWorld() 15 | return Expression.parseExpression( 16 | world, 17 | decoder.decodeString(), 18 | decoder.serializersModule 19 | ) 20 | } 21 | 22 | override fun serialize(encoder: Encoder, value: Expression<*>) { 23 | TODO("Not yet implemented") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/helpers/GearyTestTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers 2 | 3 | import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap 4 | import com.mineinabyss.geary.test.GearyTest 5 | import io.kotest.matchers.shouldBe 6 | import io.kotest.matchers.shouldNotBe 7 | import org.junit.jupiter.api.Test 8 | import org.koin.dsl.koinApplication 9 | import org.koin.dsl.module 10 | 11 | class GearyTestTest: GearyTest() { 12 | @Test 13 | fun `clear engine`() { 14 | val engine = this.engine 15 | resetEngine() 16 | this.engine shouldNotBe engine 17 | } 18 | 19 | @Test 20 | // This is testing a behaviour of koin that I didn't expect, keep this here in case it changes in the future 21 | fun `reusing koin modules should reuse exact class instances`() { 22 | val module = module { 23 | single { ArrayTypeMap() } 24 | } 25 | koinApplication { modules(module) }.koin.get() shouldBe koinApplication { modules(module) }.koin.get() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/PrefabManager.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | import com.mineinabyss.geary.datatypes.Entity 4 | 5 | /** 6 | * Manages registered prefabs and accessing them via name. 7 | */ 8 | class PrefabManager { 9 | /** A list of registered [PrefabKey]s. */ 10 | val keys: List get() = keyToPrefab.keys.toList() 11 | 12 | private val keyToPrefab: MutableMap = mutableMapOf() 13 | 14 | /** Gets a prefab by [name]. */ 15 | operator fun get(name: PrefabKey): Entity? = keyToPrefab[name] 16 | 17 | /** Registers a prefab with Geary. */ 18 | fun registerPrefab(key: PrefabKey, prefab: Entity) { 19 | keyToPrefab[key] = prefab 20 | } 21 | 22 | /** Gets all prefabs registered under a certain [namespace]. */ 23 | fun getPrefabsFor(namespace: String): List = 24 | keys.filter { it.namespace == namespace } 25 | 26 | /** Clears all stored [keyToPrefab] */ 27 | internal fun clear() { 28 | keyToPrefab.clear() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/serializers/ProvidedConfig.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.serializers 2 | 3 | import kotlinx.serialization.InternalSerializationApi 4 | import kotlinx.serialization.KSerializer 5 | import kotlinx.serialization.descriptors.PolymorphicKind 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | import kotlinx.serialization.descriptors.buildSerialDescriptor 8 | import kotlinx.serialization.encoding.Decoder 9 | import kotlinx.serialization.encoding.Encoder 10 | 11 | class ProvidedConfig(val config: PolymorphicListAsMapSerializer.Config<*>) : KSerializer { 12 | @OptIn(InternalSerializationApi::class) 13 | override val descriptor: SerialDescriptor = 14 | buildSerialDescriptor("PolymorphicListAsMapSerializer.Config", PolymorphicKind.SEALED) 15 | 16 | override fun deserialize(decoder: Decoder): ProvidedConfig { 17 | error("") 18 | } 19 | 20 | override fun serialize(encoder: Encoder, value: ProvidedConfig) { 21 | error("") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/observers/EventToObserversMap.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers 2 | 3 | import androidx.collection.LongSparseArray 4 | import com.mineinabyss.geary.datatypes.ComponentId 5 | import com.mineinabyss.geary.datatypes.getOrPut 6 | import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap 7 | 8 | class EventToObserversMap( 9 | val records: ArrayTypeMap, 10 | ) { 11 | private val eventToObserverMap = LongSparseArray() 12 | 13 | val isEmpty get() = eventToObserverMap.isEmpty() 14 | 15 | fun addObserver(observer: Observer) { 16 | observer.listenToEvents.forEach { event -> 17 | eventToObserverMap.getOrPut(event.toLong()) { ObserverList(records) }.add(observer) 18 | } 19 | } 20 | 21 | fun removeObserver(observer: Observer) { 22 | observer.listenToEvents.forEach { event -> 23 | eventToObserverMap[event.toLong()]?.remove(observer) 24 | } 25 | } 26 | 27 | operator fun get(event: ComponentId): ObserverList? = eventToObserverMap[event.toLong()] 28 | } 29 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/actions/BecomeAction.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.actions 2 | 3 | import com.mineinabyss.geary.actions.Action 4 | import com.mineinabyss.geary.actions.ActionGroupContext 5 | import com.mineinabyss.geary.actions.expressions.EntityExpression 6 | import com.mineinabyss.geary.serialization.serializers.InnerSerializer 7 | import kotlinx.serialization.SerialName 8 | import kotlinx.serialization.Serializable 9 | 10 | @Serializable(with = BecomeAction.Serializer::class) 11 | @SerialName("geary:become") 12 | class BecomeAction( 13 | val become: EntityExpression, 14 | ) : Action { 15 | override val useSubcontext: Boolean = false 16 | 17 | override fun ActionGroupContext.execute() { 18 | entity = become.evaluate(this) 19 | } 20 | 21 | object Serializer : InnerSerializer( 22 | serialName = "geary:become", 23 | inner = EntityExpression.serializer(), 24 | inverseTransform = { it.become }, 25 | transform = { BecomeAction(it) } 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/helpers/RoleHelperTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers 2 | 3 | import com.mineinabyss.geary.datatypes.* 4 | import io.kotest.matchers.shouldBe 5 | import org.junit.jupiter.api.Test 6 | 7 | class TypeRolesTest { 8 | 9 | @Test 10 | fun hasRoleTest() { 11 | val actual = 1uL or HOLDS_DATA 12 | actual.hasRole(HOLDS_DATA) shouldBe true 13 | } 14 | 15 | @Test 16 | fun hasNotRoleTest() { 17 | val actual = 1uL or HOLDS_DATA 18 | actual.hasRole(RELATION) shouldBe false 19 | } 20 | 21 | @Test 22 | fun withRoleTest() { 23 | val actual = 1uL 24 | actual.withRole(HOLDS_DATA) shouldBe (actual or HOLDS_DATA) 25 | } 26 | 27 | @Test 28 | fun withoutRoleTest() { 29 | val actual = 1uL 30 | actual.withRole(HOLDS_DATA).withoutRole(HOLDS_DATA) shouldBe actual 31 | } 32 | 33 | @Test 34 | fun alterRoleTest() { 35 | val actual = 1uL 36 | actual.withRole(HOLDS_DATA).withInvertedRole(HOLDS_DATA) shouldBe actual 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/backend/future-plans/memory-structure.md: -------------------------------------------------------------------------------- 1 | # Memory structure 2 | 3 | Many ECSes advertise their speed over object-oriented approaches. How an ECS structures its data and the benefits or downsides that come with it is a complex topic, with many structures to explore. However, a common approach in designing an ECS is to keep related data together, specifically to help avoid cache misses. 4 | 5 | The JVM has traditionally managed a lot of memory organization on its own, but through [Project Valhalla](https://en.wikipedia.org/wiki/Project_Valhalla_(Java_language)) we can hopefully get more control over the memory structure (at least ensure tightly packed data) and start thinking about optimizations from there. 6 | 7 | For anyone interested, the following talk goes over how to think about CPU cache and some of its quirks: 8 | 9 | 10 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/serializers/StructureDependantSerializer.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.serializers 2 | 3 | import kotlinx.serialization.ContextualSerializer 4 | import kotlinx.serialization.KSerializer 5 | import kotlinx.serialization.descriptors.SerialDescriptor 6 | import kotlinx.serialization.encoding.Decoder 7 | import kotlinx.serialization.encoding.decodeStructure 8 | 9 | abstract class StructureDependantSerializer: KSerializer { 10 | // Descriptor is contextual since it depends on the chosen structure 11 | override val descriptor: SerialDescriptor = ContextualSerializer(Any::class).descriptor 12 | 13 | inline fun Decoder.tryDecode( 14 | serializer: KSerializer, 15 | descriptor: SerialDescriptor = serializer.descriptor, 16 | crossinline mapOnSuccess: (T) -> R 17 | ): R? = runCatching { 18 | decodeStructure(descriptor) { 19 | val decoded = decodeSerializableElement(descriptor, 0, serializer) 20 | mapOnSuccess(decoded) 21 | } 22 | }.getOrNull() 23 | } 24 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/QueryManager.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.datatypes.Entity 4 | import com.mineinabyss.geary.datatypes.EntityId 5 | import com.mineinabyss.geary.datatypes.EntityIdArray 6 | import com.mineinabyss.geary.datatypes.family.Family 7 | import com.mineinabyss.geary.systems.query.CachedQuery 8 | import com.mineinabyss.geary.systems.query.Query 9 | 10 | interface QueryManager { 11 | fun trackQuery(query: T): CachedQuery 12 | 13 | /** Returns a list of entities matching the given family. */ 14 | fun getEntitiesMatching(family: Family): EntityIdArray 15 | 16 | /** 17 | * Returns a sequence of entities matching the given family. 18 | * This should be faster than [getEntitiesMatching] but will depend on impl. 19 | * In an archetypal engine, this gets all matching archetypes first, then maps them to entities as a sequence. 20 | */ 21 | fun getEntitiesMatchingAsSequence(family: Family): Sequence 22 | 23 | fun childrenOf(parent: EntityId): EntityIdArray 24 | } 25 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/event_binds/ParseEntityObservers.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.event_binds 2 | 3 | import com.mineinabyss.geary.actions.ActionGroupContext 4 | import com.mineinabyss.geary.actions.execute 5 | import com.mineinabyss.geary.datatypes.EntityType 6 | import com.mineinabyss.geary.modules.Geary 7 | import com.mineinabyss.geary.modules.observe 8 | import com.mineinabyss.geary.observers.entity.observe 9 | import com.mineinabyss.geary.observers.events.OnSet 10 | import com.mineinabyss.geary.systems.query.query 11 | 12 | fun Geary.bindEntityObservers() = observe() 13 | .involving(query()) 14 | .exec { (observers) -> 15 | observers.observers.forEach { observer -> 16 | val actionGroup = observer.actionGroup 17 | entity.observe(observer.event).involving(EntityType(observer.involving)).exec { 18 | val context = ActionGroupContext(entity) 19 | actionGroup.execute(context) 20 | } 21 | } 22 | entity.remove() 23 | } 24 | 25 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "geary" 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | maven("https://repo.mineinabyss.com/releases") 7 | maven("https://repo.mineinabyss.com/snapshots") 8 | mavenLocal() 9 | } 10 | } 11 | 12 | dependencyResolutionManagement { 13 | val idofrontVersion: String by settings 14 | 15 | repositories { 16 | maven("https://repo.mineinabyss.com/releases") 17 | maven("https://repo.mineinabyss.com/snapshots") 18 | mavenLocal() 19 | } 20 | 21 | versionCatalogs { 22 | create("idofrontLibs") { 23 | from("com.mineinabyss:catalog:$idofrontVersion") 24 | } 25 | } 26 | } 27 | 28 | include( 29 | "geary-benchmarks", 30 | "geary-core", 31 | "geary-test", 32 | ) 33 | 34 | // Go through addons directory and load all projects based on file name 35 | for (addon in file("addons").listFiles()) { 36 | if (addon.isDirectory) { 37 | include(addon.name) 38 | project(":${addon.name}").projectDir = file(addon) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/unpacking/Unpack2Benchmark.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.unpacking 2 | 3 | import com.mineinabyss.geary.benchmarks.helpers.Comp1 4 | import com.mineinabyss.geary.benchmarks.helpers.Comp2 5 | import com.mineinabyss.geary.benchmarks.helpers.GearyBenchmark 6 | import com.mineinabyss.geary.benchmarks.helpers.tenMil 7 | import com.mineinabyss.geary.helpers.entity 8 | import org.openjdk.jmh.annotations.Benchmark 9 | import org.openjdk.jmh.annotations.Scope 10 | import org.openjdk.jmh.annotations.Setup 11 | import org.openjdk.jmh.annotations.State 12 | 13 | @State(Scope.Benchmark) 14 | class Unpack2Benchmark : GearyBenchmark() { 15 | @Setup 16 | fun setUp() { 17 | repeat(tenMil) { 18 | entity { 19 | set(Comp1(1)) 20 | set(Comp2(1)) 21 | } 22 | } 23 | } 24 | 25 | @Benchmark 26 | fun unpack1of2Comp() { 27 | systemOf2().forEach { (a) -> } 28 | } 29 | 30 | @Benchmark 31 | fun unpack2of2Comp() { 32 | systemOf2().forEach { (a, b) -> } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /geary-core/src@native/com/mineinabyss/geary/datatypes/BitSet.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | actual class BitSet actual constructor() { 4 | actual fun isEmpty(): Boolean { 5 | TODO("Not yet implemented") 6 | } 7 | 8 | actual operator fun get(index: Int): Boolean { 9 | TODO("Not yet implemented") 10 | } 11 | 12 | actual fun set(index: Int) { 13 | } 14 | 15 | actual fun set(from: Int, to: Int) { 16 | } 17 | 18 | actual fun clear(index: Int) { 19 | } 20 | 21 | actual fun clear() { 22 | } 23 | 24 | actual fun flip(index: Int) { 25 | } 26 | 27 | actual fun and(other: BitSet) { 28 | } 29 | 30 | actual fun andNot(other: BitSet) { 31 | } 32 | 33 | actual fun or(other: BitSet) { 34 | } 35 | 36 | actual fun xor(other: BitSet) { 37 | } 38 | 39 | actual val cardinality: Int 40 | get() = TODO("Not yet implemented") 41 | 42 | actual inline fun forEachBit(crossinline loop: (Int) -> Unit) { 43 | } 44 | 45 | actual fun copy(): BitSet { 46 | TODO("Not yet implemented") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | androidxCollection = "1.4.0" 3 | atomicfu = "0.24.0" 4 | kotlinxBenchmarkRuntime = "0.4.10" 5 | okio = "3.9.0" 6 | roaringbitmap = "1.0.6" 7 | statelyConcurrency = "2.0.7" 8 | koin = "4.0.0" 9 | junitJupiter = "5.8.1" 10 | 11 | [libraries] 12 | androidx-collection = { module = "androidx.collection:collection", version.ref = "androidxCollection" } 13 | atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "atomicfu" } 14 | kotlinx-benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinxBenchmarkRuntime" } 15 | okio = { module = "com.squareup.okio:okio", version.ref = "okio" } 16 | roaringbitmap = { module = "org.roaringbitmap:RoaringBitmap", version.ref = "roaringbitmap" } 17 | stately-concurrency = { module = "co.touchlab:stately-concurrency", version.ref = "statelyConcurrency" } 18 | koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } 19 | koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } 20 | junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junitJupiter" } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Danielle Voznyy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/family/Family.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes.family 2 | 3 | import com.mineinabyss.geary.datatypes.ComponentId 4 | import com.mineinabyss.geary.datatypes.EntityId 5 | 6 | sealed interface Family { 7 | sealed class Leaf : Family { 8 | sealed interface Component : Family { 9 | val component: ComponentId 10 | } 11 | 12 | sealed interface AnyToTarget : Family { 13 | val target: EntityId 14 | val kindMustHoldData: Boolean 15 | } 16 | 17 | sealed interface KindToAny : Family { 18 | val kind: ComponentId 19 | val targetMustHoldData: Boolean 20 | } 21 | } 22 | 23 | sealed interface Selector : Family { 24 | val components: List 25 | val componentsWithData: List 26 | 27 | sealed interface And : Selector { 28 | val and: List 29 | } 30 | 31 | sealed interface AndNot : Selector { 32 | val andNot: List 33 | } 34 | 35 | sealed interface Or : Selector { 36 | val or: List 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/modules/GearyModule.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.modules 2 | 3 | import org.koin.core.module.Module 4 | import org.koin.dsl.koinApplication 5 | 6 | fun geary( 7 | module: GearyModule, 8 | configure: GearySetup.() -> Unit = {}, 9 | ): UninitializedGearyModule { 10 | val application = koinApplication { 11 | properties(module.properties) 12 | modules(module.module) 13 | } 14 | val initializer = application.koin.get() 15 | initializer.init() 16 | val setup = GearySetup(application) 17 | configure(setup) 18 | return UninitializedGearyModule(setup, initializer) 19 | } 20 | 21 | data class UninitializedGearyModule( 22 | val setup: GearySetup, 23 | val initializer: EngineInitializer, 24 | ) { 25 | inline fun configure(configure: GearySetup.() -> Unit): UninitializedGearyModule = apply { setup.configure() } 26 | 27 | fun start(): Geary { 28 | val world = Geary(setup.application) 29 | world.addons.initAll(setup) 30 | initializer.start() 31 | return world 32 | } 33 | } 34 | 35 | data class GearyModule( 36 | val module: Module, 37 | val properties: Map = emptyMap(), 38 | ) 39 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/instantiation/ManyComponentsBenchmark.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.instantiation 2 | 3 | import co.touchlab.kermit.Logger 4 | import co.touchlab.kermit.Severity 5 | import com.mineinabyss.geary.helpers.entity 6 | import com.mineinabyss.geary.modules.Geary 7 | import com.mineinabyss.geary.modules.TestEngineModule 8 | import com.mineinabyss.geary.modules.geary 9 | import org.openjdk.jmh.annotations.* 10 | 11 | @State(Scope.Benchmark) 12 | class ManyComponentsBenchmark { 13 | @Setup 14 | fun setLoggingLevel() { 15 | Logger.setMinSeverity(Severity.Warn) 16 | } 17 | 18 | var geary: Geary = geary(TestEngineModule).start() 19 | 20 | @Setup(Level.Invocation) 21 | fun setupPerInvocation() { 22 | geary = geary(TestEngineModule).start() 23 | } 24 | 25 | @Benchmark 26 | fun createTenThousandEntitiesWithUniqueComponentEach() = with(geary) { 27 | repeat(10000) { 28 | entity { 29 | addRelation(it.toLong().toGeary()) 30 | } 31 | } 32 | } 33 | } 34 | 35 | fun main() { 36 | geary(TestEngineModule) 37 | ManyComponentsBenchmark().createTenThousandEntitiesWithUniqueComponentEach() 38 | } 39 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/event_binds/EntityObservers.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.event_binds 2 | 3 | import com.mineinabyss.geary.actions.Tasks 4 | import com.mineinabyss.geary.datatypes.ComponentId 5 | import com.mineinabyss.geary.serialization.serializers.InnerSerializer 6 | import com.mineinabyss.geary.serialization.serializers.SerializableComponentId 7 | import kotlinx.serialization.ContextualSerializer 8 | import kotlinx.serialization.Serializable 9 | import kotlinx.serialization.builtins.MapSerializer 10 | 11 | @Serializable(with = EntityObservers.Serializer::class) 12 | class EntityObservers( 13 | val observers: List, 14 | ) { 15 | class Serializer : InnerSerializer, EntityObservers>( 16 | serialName = "geary:observe", 17 | inner = MapSerializer( 18 | ContextualSerializer(ComponentId::class), 19 | Tasks.serializer() 20 | ), 21 | inverseTransform = { TODO() }, 22 | transform = { 23 | EntityObservers( 24 | it.map { (event, actionGroup) -> 25 | EventBind(event, actionGroup = actionGroup) 26 | } 27 | ) 28 | } 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/dsl/builders/FormatsBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.dsl.builders 2 | 3 | import com.mineinabyss.geary.serialization.ComponentSerializers 4 | import com.mineinabyss.geary.serialization.formats.Format 5 | import com.mineinabyss.geary.serialization.formats.Formats 6 | import com.mineinabyss.geary.serialization.formats.SimpleFormats 7 | import kotlinx.serialization.cbor.Cbor 8 | import kotlinx.serialization.modules.SerializersModule 9 | 10 | data class FormatsBuilder( 11 | val formats: MutableMap Format> = mutableMapOf() 12 | ) { 13 | /** Registers a [Format] for a file with extension [ext]. */ 14 | fun register(ext: String, makeFromat: (SerializersModule) -> Format) { 15 | formats[ext] = makeFromat 16 | } 17 | 18 | fun build(serializers: ComponentSerializers): Formats { 19 | 20 | return SimpleFormats( 21 | binaryFormat = Cbor { 22 | serializersModule = serializers.module 23 | encodeDefaults = false 24 | ignoreUnknownKeys = true 25 | }, 26 | formats = formats.mapValues { it.value(serializers.module) }, 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/Components.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.components.CouldHaveChildren 4 | import com.mineinabyss.geary.components.KeepEmptyArchetype 5 | import com.mineinabyss.geary.components.relations.ChildOf 6 | import com.mineinabyss.geary.components.relations.InstanceOf 7 | import com.mineinabyss.geary.components.relations.NoInherit 8 | import com.mineinabyss.geary.observers.Observer 9 | import com.mineinabyss.geary.observers.events.* 10 | 11 | class Components( 12 | comp: ComponentProvider, 13 | ) { 14 | val any = comp.id() 15 | val suppressRemoveEvent = comp.id() 16 | val couldHaveChildren = comp.id() 17 | val observer = comp.id() 18 | val onAdd = comp.id() 19 | val onSet = comp.id() 20 | val onFirstSet = comp.id() 21 | val onUpdate = comp.id() 22 | val onRemove = comp.id() 23 | val onExtend = comp.id() 24 | val onEntityRemoved = comp.id() 25 | val childOf = comp.id() 26 | val instanceOf = comp.id() 27 | val noInherit = comp.id() 28 | val keepEmptyArchetype = comp.id() 29 | } 30 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/accessors/type/ComponentOrDefaultAccessor.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.accessors.type 2 | 3 | import com.mineinabyss.geary.annotations.optin.UnsafeAccessors 4 | import com.mineinabyss.geary.datatypes.ComponentId 5 | import com.mineinabyss.geary.engine.archetypes.Archetype 6 | import com.mineinabyss.geary.systems.accessors.Accessor 7 | import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor 8 | import com.mineinabyss.geary.systems.query.Query 9 | import kotlin.reflect.KProperty 10 | 11 | class ComponentOrDefaultAccessor( 12 | override val originalAccessor: Accessor?, 13 | val id: ComponentId, 14 | val default: () -> T, 15 | ) : ReadOnlyAccessor { 16 | private var cachedIndex = -1 17 | private var cachedArchetype: Archetype? = null 18 | 19 | @OptIn(UnsafeAccessors::class) 20 | override fun get(query: Query): T { 21 | val archetype = query.archetype 22 | if (archetype !== cachedArchetype) { 23 | cachedArchetype = archetype 24 | cachedIndex = archetype.indexOf(id) 25 | } 26 | if (cachedIndex == -1) return default() 27 | @Suppress("UNCHECKED_CAST") 28 | return archetype.componentData[cachedIndex][query.row] as T 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/observers/EntityRemoveObserverTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers 2 | 3 | import com.mineinabyss.geary.helpers.entity 4 | import com.mineinabyss.geary.test.GearyTest 5 | import com.mineinabyss.geary.modules.observe 6 | import com.mineinabyss.geary.observers.events.OnEntityRemoved 7 | import com.mineinabyss.geary.systems.query.query 8 | import io.kotest.matchers.shouldBe 9 | import kotlin.test.Test 10 | 11 | class EntityRemoveObserverTest : GearyTest() { 12 | @Test 13 | fun `should correctly run multiple listeners on single event`() { 14 | var called = 0 15 | 16 | val listener1 = observe().exec(query()) { (data) -> 17 | data shouldBe 1 18 | entity.remove() 19 | called++ 20 | } 21 | 22 | val listener2 = observe().exec(query()) { (data) -> 23 | data shouldBe "" 24 | } 25 | 26 | val entity1 = entity { 27 | set(1) 28 | set("") 29 | } 30 | 31 | val entity2 = entity { 32 | set(1) 33 | set("") 34 | } 35 | 36 | entity2.removeEntity() 37 | entity1.removeEntity() 38 | called shouldBe 2 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/configuration/components/ChildrenOnPrefab.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.configuration.components 2 | 3 | import com.mineinabyss.geary.components.EntityName 4 | import com.mineinabyss.geary.datatypes.Component 5 | import com.mineinabyss.geary.serialization.serializers.InnerSerializer 6 | import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer 7 | import kotlinx.serialization.Polymorphic 8 | import kotlinx.serialization.Serializable 9 | import kotlinx.serialization.builtins.MapSerializer 10 | import kotlinx.serialization.builtins.serializer 11 | 12 | /** 13 | * > geary:children 14 | * 15 | * A component that will add a list of named children to this entity. 16 | * 17 | * The keys will be used to set an extra [EntityName] component. 18 | */ 19 | @Serializable(with = ChildrenOnPrefab.Serializer::class) 20 | class ChildrenOnPrefab( 21 | val nameToComponents: Map> 22 | ) { 23 | class Serializer : InnerSerializer>, ChildrenOnPrefab>( 24 | "geary:children", 25 | MapSerializer(String.serializer(), PolymorphicListAsMapSerializer.ofComponents()), 26 | { ChildrenOnPrefab(it) }, 27 | { it.nameToComponents }, 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/helpers/InheritPrefabs.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.helpers 2 | 3 | import com.mineinabyss.geary.datatypes.Entity 4 | import com.mineinabyss.geary.modules.geary 5 | import com.mineinabyss.geary.prefabs.PrefabKey 6 | import com.mineinabyss.geary.prefabs.configuration.components.InheritPrefabs 7 | import com.mineinabyss.geary.prefabs.entityOfOrNull 8 | 9 | /** 10 | * Adds prefabs to this entity from an [InheritPrefabs] component. Will make sure parents have their prefabs 11 | * added from this component before trying to add it 12 | */ 13 | fun Entity.inheritPrefabsIfNeeded(instances: Set = setOf()): Unit = with(world) { 14 | if (this@inheritPrefabsIfNeeded in instances) 15 | error("Circular dependency found while loading prefabs for ${get()}, chain was: $instances") 16 | val add = get() ?: return 17 | remove() 18 | add.from.mapNotNull { key -> 19 | entityOfOrNull(key).also { 20 | if (it == null) logger.w("Prefab ${get()} could not inherit prefab $key, it does not exist") 21 | } 22 | }.forEach { parent -> 23 | parent.inheritPrefabsIfNeeded(instances + this@inheritPrefabsIfNeeded) 24 | extend(parent) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish Documentation 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - 'docs/**' 10 | - .github/workflows/docs.yml 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | jobs: 19 | build: 20 | name: Build docs 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: MineInAbyss/publish-action@v3 26 | with: 27 | publish-github-release: false 28 | build-tasks: 'docsGenerate' 29 | 30 | - name: Update HTML links for GitHub Pages subdomain 31 | run: | 32 | find out -name "*.html" -exec sed -i 's#href="/#href="/geary/#g; s#src="/#src="/geary/#g' {} \; 33 | 34 | - name: Upload artifact 35 | uses: actions/upload-pages-artifact@v3 36 | with: 37 | path: out 38 | 39 | deploy: 40 | environment: 41 | name: github-pages 42 | url: ${{ steps.deployment.outputs.page_url }} 43 | runs-on: ubuntu-latest 44 | needs: build 45 | steps: 46 | - name: Deploy to GitHub Pages 47 | id: deployment 48 | uses: actions/deploy-pages@v4 49 | -------------------------------------------------------------------------------- /addons/geary-prefabs/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(idofrontLibs.plugins.mia.kotlin.multiplatform.get().pluginId) 3 | id(idofrontLibs.plugins.mia.publication.get().pluginId) 4 | alias(idofrontLibs.plugins.kotlinx.serialization) 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | commonMain { 10 | dependencies { 11 | implementation(project(":geary-core")) 12 | implementation(project(":geary-serialization")) 13 | } 14 | kotlin.srcDir("src") 15 | } 16 | 17 | jvmMain { 18 | kotlin.srcDir("src@jvm") 19 | } 20 | 21 | jvmTest { 22 | dependencies { 23 | implementation(kotlin("test")) 24 | implementation(project(":geary-test")) 25 | implementation(idofrontLibs.kotlinx.coroutines.test) 26 | implementation(idofrontLibs.kotest.assertions) 27 | implementation(idofrontLibs.kotest.property) 28 | implementation(project(":geary-core")) 29 | implementation(project(":geary-serialization")) 30 | implementation(idofrontLibs.junit.jupiter) 31 | } 32 | kotlin.srcDir("test@jvm") 33 | resources.srcDir("resources") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/configuration/components/InstancesOnPrefab.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.configuration.components 2 | 3 | import com.mineinabyss.geary.components.EntityName 4 | import com.mineinabyss.geary.datatypes.Component 5 | import com.mineinabyss.geary.serialization.serializers.InnerSerializer 6 | import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer 7 | import kotlinx.serialization.Polymorphic 8 | import kotlinx.serialization.Serializable 9 | import kotlinx.serialization.builtins.MapSerializer 10 | import kotlinx.serialization.builtins.serializer 11 | 12 | /** 13 | * > geary:children 14 | * 15 | * A component that will add a list of named children to this entity. 16 | * 17 | * The keys will be used to set an extra [EntityName] component. 18 | */ 19 | @Serializable(with = InstancesOnPrefab.Serializer::class) 20 | class InstancesOnPrefab( 21 | val nameToComponents: Map> 22 | ) { 23 | class Serializer : InnerSerializer>, InstancesOnPrefab>( 24 | "geary:instances", 25 | MapSerializer(String.serializer(), PolymorphicListAsMapSerializer.ofComponents()), 26 | { InstancesOnPrefab(it) }, 27 | { it.nameToComponents }, 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/query/Query.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.query 2 | 3 | import com.mineinabyss.geary.modules.ArchetypeEngineModule 4 | import com.mineinabyss.geary.modules.Geary 5 | import com.mineinabyss.geary.systems.accessors.Accessor 6 | import com.mineinabyss.geary.systems.accessors.type.ComponentAccessor 7 | import kotlin.reflect.KProperty 8 | 9 | abstract class Query(world: Geary) : QueriedEntity(world, cacheAccessors = true) { 10 | /** Automatically matches families for any accessor that's supposed to match a family. */ 11 | operator fun T.provideDelegate( 12 | thisRef: Any, 13 | prop: KProperty<*> 14 | ): T { 15 | props[prop.name] = this 16 | return this 17 | } 18 | 19 | protected open fun ensure() {} 20 | 21 | @PublishedApi 22 | internal fun initialize() { 23 | ensure() 24 | } 25 | 26 | // Optional helpers for avoiding delegates in accessors 27 | 28 | @Suppress("NOTHING_TO_INLINE") // These functions are here for maximum speed over delegates, we can inline :) 29 | inline operator fun ComponentAccessor.invoke(): T = get(this@Query) 30 | 31 | @Suppress("NOTHING_TO_INLINE") 32 | inline fun ComponentAccessor.set(value: T) = set(this@Query, value) 33 | } 34 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/helpers/GearyTypeFamilyHelpers.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers 2 | 3 | import com.mineinabyss.geary.datatypes.* 4 | import com.mineinabyss.geary.datatypes.family.Family 5 | 6 | fun EntityType.hasRelationTarget( 7 | target: EntityId, 8 | kindMustHoldData: Boolean = false 9 | ): Boolean = any { 10 | it.isRelation() && Relation.of(it).run { 11 | this.target == target && (!kindMustHoldData || contains(this.kind.withRole(HOLDS_DATA))) 12 | } 13 | } 14 | 15 | fun EntityType.hasRelationKind( 16 | kind: ComponentId, 17 | targetMustHoldData: Boolean = false 18 | ): Boolean = any { 19 | it.isRelation() && Relation.of(it).run { 20 | this.kind == kind && (!targetMustHoldData || contains(this.target.withRole(HOLDS_DATA))) 21 | } 22 | } 23 | 24 | operator fun Family.contains(type: EntityType): Boolean = has(type) 25 | 26 | fun Family.has(type: EntityType): Boolean = when (this) { 27 | is Family.Selector.And -> and.all { type in it } 28 | is Family.Selector.AndNot -> andNot.none { type in it } 29 | is Family.Selector.Or -> or.any { type in it } 30 | is Family.Leaf.Component -> component in type 31 | is Family.Leaf.KindToAny -> type.hasRelationKind(kind, targetMustHoldData) 32 | is Family.Leaf.AnyToTarget -> type.hasRelationTarget(target, kindMustHoldData) 33 | } 34 | -------------------------------------------------------------------------------- /addons/geary-prefabs/test@jvm/com/mineinabyss/geary/prefabs/PrefabTests.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | import com.mineinabyss.geary.helpers.entity 4 | import com.mineinabyss.geary.modules.TestEngineModule 5 | import com.mineinabyss.geary.modules.geary 6 | import com.mineinabyss.geary.serialization.SerializableComponents 7 | import com.mineinabyss.geary.test.GearyTest 8 | import io.kotest.matchers.nulls.shouldBeNull 9 | import io.kotest.matchers.shouldBe 10 | import org.junit.jupiter.api.Test 11 | 12 | class PrefabTests : GearyTest() { 13 | private val testKey = PrefabKey.of("test:1") 14 | 15 | override fun setupGeary() = geary(TestEngineModule) { 16 | install(SerializableComponents) 17 | install(Prefabs) 18 | } 19 | 20 | @Test 21 | fun `should not inherit prefab keys by default`() { 22 | // arrange 23 | val prefab = entity { set(testKey) } 24 | 25 | // act 26 | val instance = entity { extend(prefab) } 27 | 28 | // assert 29 | entityOfOrNull(testKey) shouldBe prefab 30 | instance.get().shouldBeNull() 31 | } 32 | 33 | @Test 34 | fun `should track prefabs when key added`() { 35 | // arrange & act 36 | val prefab = entity { set(testKey) } 37 | 38 | // assert 39 | entityOfOrNull(testKey) shouldBe prefab 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /addons/geary-uuid/src/com/mineinabyss/geary/uuid/UUIDTracking.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.uuid 2 | 3 | import com.mineinabyss.geary.addons.dsl.createAddon 4 | import com.mineinabyss.geary.modules.observe 5 | import com.mineinabyss.geary.observers.events.OnRemove 6 | import com.mineinabyss.geary.observers.events.OnSet 7 | import com.mineinabyss.geary.systems.query.query 8 | import com.mineinabyss.geary.uuid.components.RegenerateUUIDOnClash 9 | import kotlin.uuid.Uuid 10 | 11 | val UUIDTracking = createAddon("UUID Tracking", { SimpleUUID2GearyMap() }) { 12 | systems { 13 | observe("Track UUID on add").involving(query()).exec { (uuid) -> 14 | val regenerateUUIDOnClash = entity.has() 15 | if (uuid in configuration) 16 | if (regenerateUUIDOnClash) { 17 | val newUuid = Uuid.random() 18 | entity.set(newUuid) 19 | configuration[newUuid] = entity.id 20 | } else error("Tried tracking entity $entity with already existing uuid $uuid") 21 | else 22 | configuration[uuid] = entity.id 23 | } 24 | 25 | observe("Untrack UUID on remove") 26 | .involving(query()) 27 | .exec { (uuid) -> configuration.remove(uuid) } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/ComponentSerializers.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization 2 | 3 | import com.mineinabyss.geary.datatypes.Component 4 | import kotlinx.serialization.DeserializationStrategy 5 | import kotlinx.serialization.KSerializer 6 | import kotlinx.serialization.modules.SerializersModule 7 | import kotlin.reflect.KClass 8 | 9 | interface ComponentSerializers { 10 | val module: SerializersModule 11 | 12 | //TODO allow this to work for all registered classes, not just components 13 | fun getClassFor(serialName: String, namespaces: List = listOf()): KClass 14 | 15 | fun getSerializerFor( 16 | key: String, 17 | baseClass: KClass 18 | ): DeserializationStrategy? 19 | 20 | fun getSerializerFor(kClass: KClass): DeserializationStrategy? 21 | fun getSerialNameFor(kClass: KClass): String? 22 | 23 | fun getKClassFor(serializer: KSerializer): KClass? 24 | 25 | companion object { 26 | private val camelRegex = Regex("([A-Z])") 27 | fun String.fromCamelCaseToSnakeCase(): String { 28 | return this.replace(camelRegex, "_$1").removePrefix("_").lowercase() 29 | } 30 | fun String.hasNamespace(): Boolean = contains(":") 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/ActionGroupContext.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions 2 | 3 | import com.mineinabyss.geary.actions.expressions.Expression 4 | import com.mineinabyss.geary.datatypes.GearyEntity 5 | import kotlinx.coroutines.CoroutineScope 6 | 7 | class ActionGroupContext() { 8 | constructor(entity: GearyEntity) : this() { 9 | this.entity = entity 10 | } 11 | 12 | var entity: GearyEntity? 13 | get() = environment["entity"] as? GearyEntity 14 | set(value) { 15 | environment["entity"] = value 16 | } 17 | 18 | val coroutineScope: CoroutineScope? get() = entity?.world?.engine?.mainScope 19 | 20 | val environment: MutableMap = mutableMapOf() 21 | 22 | fun eval(expression: Expression): T = expression.evaluate(this) 23 | 24 | fun register(name: String, value: Any?) { 25 | environment[name] = value 26 | } 27 | 28 | fun copy(): ActionGroupContext { 29 | val newContext = ActionGroupContext() 30 | newContext.environment.putAll(environment) 31 | return newContext 32 | } 33 | 34 | fun plus(newEnvironment: Map): ActionGroupContext { 35 | val newContext = copy() 36 | newContext.environment.putAll(newEnvironment) 37 | return newContext 38 | } 39 | 40 | companion object 41 | } 42 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/EntityReadOperations.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.datatypes.* 4 | import com.mineinabyss.geary.systems.accessors.RelationWithData 5 | 6 | interface EntityReadOperations { 7 | /** Gets a [componentId]'s data from an [entity] or null if not present/the component doesn't hold any data. */ 8 | fun get(entity: EntityId, componentId: ComponentId): Component? 9 | 10 | /** Gets a list of all the components [entity] has, as well as relations in the form of [RelationComponent]. */ 11 | fun getAll(entity: EntityId): Array 12 | 13 | /** Checks whether an [entity] is still active in the engine. */ 14 | fun exists(entity: EntityId): Boolean 15 | 16 | /** 17 | * Gets relations in the same format as [Archetype.getRelations], but when kind/target [HOLDS_DATA], the appropriate 18 | * data is written to a [RelationWithData] object. 19 | */ 20 | fun getRelationsWithDataFor( 21 | entity: EntityId, 22 | kind: ComponentId, 23 | target: EntityId 24 | ): List> 25 | 26 | fun getRelationsFor(entity: EntityId, kind: ComponentId, target: EntityId): List 27 | 28 | 29 | /** Checks whether an [entity] has a [componentId] */ 30 | fun has(entity: EntityId, componentId: ComponentId): Boolean 31 | 32 | } 33 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/BucketedULongArray.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | import androidx.collection.mutableObjectListOf 4 | 5 | private const val bucketSize: Int = 1024 6 | 7 | class BucketedULongArray { 8 | private val buckets = mutableObjectListOf() 9 | var maxSupportedSize = 0 10 | private set 11 | var size = 0 12 | private set 13 | 14 | val lastIndex get() = size - 1 15 | 16 | operator fun get(index: Int): ULong { 17 | val bucketIndex = index / bucketSize 18 | val bucket = buckets[bucketIndex] 19 | return bucket[index % bucketSize].toULong() 20 | } 21 | 22 | fun ensureSize(including: Int) { 23 | var maxSupportedSize = maxSupportedSize 24 | while (including >= maxSupportedSize) { 25 | buckets.add(LongArray(bucketSize)) 26 | maxSupportedSize += bucketSize 27 | } 28 | this.maxSupportedSize = maxSupportedSize 29 | } 30 | 31 | operator fun set(index: Int, value: ULong) { 32 | val bucketIndex = index / bucketSize 33 | ensureSize(index) 34 | val bucket = buckets[bucketIndex] 35 | if (index >= size) size = index + 1 36 | bucket[index % bucketSize] = value.toLong() 37 | } 38 | 39 | fun add(value: ULong) { 40 | val index = size 41 | set(index, value) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/queries/accessors/MappedAccessorTests.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.queries.accessors 2 | 3 | import com.mineinabyss.geary.helpers.entity 4 | import com.mineinabyss.geary.test.GearyTest 5 | import com.mineinabyss.geary.systems.query.Query 6 | import io.kotest.matchers.collections.shouldContainExactly 7 | import io.kotest.matchers.shouldBe 8 | import org.junit.jupiter.api.Test 9 | 10 | internal class MappedAccessorTests : GearyTest() { 11 | private class Marker 12 | 13 | private fun mappedQuery() = cache(object : Query(this) { 14 | val mapped by get().map { it.toString() } 15 | }) 16 | 17 | private fun defaultingQuery() = cache(object : Query(this) { 18 | val default by get().orDefault { "empty!" } 19 | override fun ensure() = this { has() } 20 | }) 21 | 22 | @Test 23 | fun `should correctly get mapped accessors`() { 24 | entity { 25 | set(1) 26 | } 27 | mappedQuery().forEach { 28 | it.mapped shouldBe "1" 29 | } 30 | } 31 | 32 | @Test 33 | fun `should correctly get default accessors`() { 34 | entity { 35 | set("Hello") 36 | add() 37 | } 38 | entity { 39 | add() 40 | } 41 | defaultingQuery().map { it.default }.shouldContainExactly("Hello", "empty!") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.unpacking 2 | 3 | import com.mineinabyss.geary.benchmarks.helpers.* 4 | import com.mineinabyss.geary.helpers.entity 5 | import com.mineinabyss.geary.systems.query.query 6 | import org.openjdk.jmh.annotations.Benchmark 7 | import org.openjdk.jmh.annotations.Scope 8 | import org.openjdk.jmh.annotations.Setup 9 | import org.openjdk.jmh.annotations.State 10 | 11 | @State(Scope.Benchmark) 12 | class Unpack6Benchmark : GearyBenchmark() { 13 | @Setup 14 | fun setUp() { 15 | repeat(tenMil) { 16 | entity { 17 | set(Comp1(0)) 18 | set(Comp2(0)) 19 | set(Comp3(0)) 20 | set(Comp4(0)) 21 | set(Comp5(0)) 22 | set(Comp6(0)) 23 | } 24 | } 25 | } 26 | 27 | @Benchmark 28 | fun unpack1of6Comp() { 29 | systemOf6().forEach { (a) -> 30 | } 31 | } 32 | 33 | @Benchmark 34 | fun unpack6of6Comp() { 35 | systemOf6().forEach { (a, b, c, d, e, f) -> 36 | } 37 | } 38 | } 39 | 40 | fun main() { 41 | Unpack6Benchmark().apply { 42 | setUp() 43 | val query = cache(query()) 44 | repeat(10000) { 45 | query.forEach { (a, b, c, d, e, f) -> 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/events/EventCalls.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.events 2 | 3 | import com.mineinabyss.geary.benchmarks.helpers.oneMil 4 | import com.mineinabyss.geary.datatypes.Entity 5 | import com.mineinabyss.geary.helpers.entity 6 | import com.mineinabyss.geary.modules.Geary 7 | import com.mineinabyss.geary.modules.TestEngineModule 8 | import com.mineinabyss.geary.modules.geary 9 | import com.mineinabyss.geary.modules.observe 10 | import org.openjdk.jmh.annotations.* 11 | 12 | @State(Scope.Benchmark) 13 | class EventCalls { 14 | private class TestEvent 15 | 16 | var targets = emptyList() 17 | var geary: Geary = geary(TestEngineModule).start() 18 | 19 | @Setup(Level.Invocation) 20 | fun setupPerInvocation() { 21 | geary = geary(TestEngineModule).start() 22 | targets = (1..oneMil).map { geary.entity().apply { set(it) } } 23 | createListener() 24 | } 25 | 26 | var count = 0 27 | 28 | fun createListener() = geary.observe().exec { 29 | count++ 30 | } 31 | 32 | @Benchmark 33 | fun callEventOn1MillionEntities() { 34 | repeat(oneMil) { 35 | targets[it].emit() 36 | } 37 | } 38 | } 39 | 40 | fun main() { 41 | geary(TestEngineModule) 42 | EventCalls().apply { 43 | setupPerInvocation() 44 | repeat(1000) { 45 | callEventOn1MillionEntities() 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/modules/MutableAddons.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.modules 2 | 3 | import com.mineinabyss.geary.addons.dsl.Addon 4 | 5 | class MutableAddons { 6 | data class AddonToConfig(val addon: Addon, val config: T) 7 | 8 | @PublishedApi 9 | internal val addons = mutableMapOf>() 10 | internal val instances = mutableMapOf() 11 | internal val addonsOrder = mutableListOf>() 12 | 13 | fun getInstance(addon: Addon<*, I>): I? { 14 | return instances[addon.name] as? I 15 | } 16 | 17 | fun getOrPut(setup: GearySetup, addon: Addon, customConfiguration: (() -> T)? = null): AddonToConfig { 18 | return addons.getOrPut(addon.name) { 19 | AddonToConfig(addon, customConfiguration?.invoke() ?: addon.defaultConfiguration(setup)) 20 | .also { addonsOrder.add(it) } 21 | } as AddonToConfig 22 | } 23 | 24 | fun getConfig(addon: Addon): T { 25 | return addons[addon.name]?.config as? T ?: error("Config for addon ${addon.name} not found") 26 | } 27 | 28 | fun init(addon: AddonToConfig<*>, setup: GearySetup) { 29 | instances[addon.addon.name] = (addon.addon.onInstall as GearySetup.(Any?) -> Any).invoke(setup, addon.config) 30 | } 31 | 32 | fun initAll(setup: GearySetup) { 33 | addonsOrder.forEach { addon -> 34 | init(addon, setup) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/koin/ArchetypeEngineModuleCheck.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.koin 2 | 3 | import co.touchlab.kermit.LoggerConfig 4 | import com.mineinabyss.geary.engine.archetypes.operations.ArchetypeReadOperations 5 | import com.mineinabyss.geary.modules.ArchetypeEngineModule 6 | import com.mineinabyss.geary.modules.ArchetypesModules 7 | import com.mineinabyss.geary.modules.TestEngineModule 8 | import com.mineinabyss.geary.modules.geary 9 | import org.junit.jupiter.api.Test 10 | import org.koin.core.annotation.KoinExperimentalAPI 11 | import org.koin.core.module.Module 12 | import org.koin.dsl.koinApplication 13 | import org.koin.test.check.checkModules 14 | import org.koin.test.verify.verify 15 | import kotlin.coroutines.CoroutineContext 16 | import kotlin.time.Duration 17 | 18 | class ArchetypeEngineModuleCheck { 19 | @OptIn(KoinExperimentalAPI::class) 20 | @Test 21 | fun checkKoinModule() { 22 | ArchetypeEngineModule().module.verify( 23 | extraTypes = listOf(Boolean::class, Duration::class, LoggerConfig::class, CoroutineContext::class, Function0::class) 24 | ) 25 | } 26 | 27 | @Test 28 | fun createKoinModule() { 29 | koinApplication { 30 | properties(ArchetypeEngineModule().properties) 31 | modules(ArchetypeEngineModule().module) 32 | checkModules() 33 | } 34 | } 35 | 36 | @Test 37 | fun startGeary() { 38 | geary(TestEngineModule).start() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/EntityMutateOperations.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.datatypes.Component 4 | import com.mineinabyss.geary.datatypes.ComponentId 5 | import com.mineinabyss.geary.datatypes.Entity 6 | import com.mineinabyss.geary.datatypes.EntityId 7 | 8 | interface EntityMutateOperations { 9 | /** 10 | * Sets [data] under a [componentId] for an [entity]. 11 | * 12 | * @param noEvent Whether to fire an [AddedComponent] event. 13 | */ 14 | fun setComponentFor( 15 | entity: EntityId, 16 | componentId: ComponentId, 17 | data: Component, 18 | noEvent: Boolean 19 | ) 20 | 21 | /** Adds this [componentId] to the [entity]'s type but doesn't store any data. */ 22 | fun addComponentFor(entity: EntityId, componentId: ComponentId, noEvent: Boolean) 23 | 24 | fun extendFor(entity: EntityId, base: EntityId) 25 | 26 | /** Removes a [componentId] from an [entity] and clears any data previously associated with it. */ 27 | fun removeComponentFor(entity: EntityId, componentId: ComponentId, noEvent: Boolean): Boolean 28 | 29 | // To avoid breaking changes from component remove events, marked for removal 30 | @Deprecated("Use removeComponentFor(entity, componentId, noEvent) instead.") 31 | fun removeComponentFor(entity: EntityId, componentId: ComponentId): Boolean 32 | 33 | /** Removes all components from an entity. */ 34 | fun clearEntity(entity: EntityId) 35 | } 36 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/serializers/GearyEntitySerializer.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.serializers 2 | 3 | import com.mineinabyss.geary.datatypes.GearyEntity 4 | import com.mineinabyss.geary.helpers.entity 5 | import com.mineinabyss.geary.modules.Geary 6 | import com.mineinabyss.geary.serialization.getAllPersisting 7 | import com.mineinabyss.geary.serialization.getWorld 8 | import kotlinx.serialization.Contextual 9 | import kotlinx.serialization.KSerializer 10 | import kotlinx.serialization.Serializable 11 | import kotlinx.serialization.descriptors.SerialDescriptor 12 | import kotlinx.serialization.encoding.Decoder 13 | import kotlinx.serialization.encoding.Encoder 14 | 15 | //TODO register contextual serializer for world 16 | typealias SerializableGearyEntity = @Contextual GearyEntity 17 | 18 | class GearyEntitySerializer() : KSerializer { 19 | private val componentSerializer = PolymorphicListAsMapSerializer.ofComponents() 20 | override val descriptor = SerialDescriptor("geary:entity", componentSerializer.descriptor) 21 | 22 | override fun deserialize(decoder: Decoder): GearyEntity { 23 | val world = decoder.serializersModule.getWorld() 24 | return world.entity { 25 | setAll(componentSerializer.deserialize(decoder)) 26 | } 27 | } 28 | 29 | override fun serialize(encoder: Encoder, value: GearyEntity) { 30 | encoder.encodeSerializableValue(componentSerializer, value.getAllPersisting().toList()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/expressions/FunctionExpression.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.expressions 2 | 3 | import com.mineinabyss.geary.actions.ActionGroupContext 4 | import com.mineinabyss.geary.modules.Geary 5 | import com.mineinabyss.geary.serialization.SerializableComponents 6 | import com.mineinabyss.geary.serialization.serializers.ComponentIdSerializer 7 | import kotlinx.serialization.modules.SerializersModule 8 | 9 | interface FunctionExpression { 10 | companion object { 11 | fun parse( 12 | world: Geary, 13 | ref: Expression<*>, 14 | name: String, 15 | yaml: String, 16 | module: SerializersModule, 17 | ): FunctionExpressionWithInput<*, *> { 18 | val serializableComponents = world.getAddon(SerializableComponents) 19 | val compClass = ComponentIdSerializer(serializableComponents.serializers, world).getComponent(name, module) 20 | val serializer = serializableComponents.serializers.getSerializerFor(compClass) 21 | ?: error("No serializer found for component $name") 22 | val expr = 23 | serializableComponents.formats["yml"]!!.decodeFromString>(serializer, yaml) 24 | return FunctionExpressionWithInput(ref, expr) 25 | } 26 | } 27 | 28 | fun ActionGroupContext.map(input: I): O 29 | 30 | fun map(input: I, context: ActionGroupContext): O { 31 | return with(context) { map(input) } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/accessors/type/ComponentAccessor.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.accessors.type 2 | 3 | import com.mineinabyss.geary.annotations.optin.UnsafeAccessors 4 | import com.mineinabyss.geary.datatypes.ComponentId 5 | import com.mineinabyss.geary.datatypes.family.family 6 | import com.mineinabyss.geary.engine.ComponentProvider 7 | import com.mineinabyss.geary.engine.archetypes.Archetype 8 | import com.mineinabyss.geary.systems.accessors.Accessor 9 | import com.mineinabyss.geary.systems.accessors.FamilyMatching 10 | import com.mineinabyss.geary.systems.accessors.ReadWriteAccessor 11 | import com.mineinabyss.geary.systems.query.Query 12 | 13 | @OptIn(UnsafeAccessors::class) 14 | class ComponentAccessor( 15 | comp: ComponentProvider, 16 | override val originalAccessor: Accessor?, 17 | val id: ComponentId, 18 | ) : ReadWriteAccessor, FamilyMatching { 19 | override val family = family { hasSet(id) } 20 | 21 | private var cachedIndex = -1 22 | private var cachedDataArray: Array = arrayOf() as Array 23 | 24 | fun updateCache(archetype: Archetype) { 25 | cachedIndex = archetype.indexOf(id) 26 | @Suppress("UNCHECKED_CAST") 27 | if (cachedIndex != -1) cachedDataArray = 28 | archetype.componentData[cachedIndex].content as Array 29 | } 30 | 31 | override fun get(query: Query): T { 32 | return cachedDataArray[query.row] 33 | } 34 | 35 | override fun set(query: Query, value: T) { 36 | cachedDataArray[query.row] = value 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/accessors/type/RelationsAccessor.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.accessors.type 2 | 3 | import com.mineinabyss.geary.annotations.optin.UnsafeAccessors 4 | import com.mineinabyss.geary.datatypes.ComponentId 5 | import com.mineinabyss.geary.datatypes.EntityId 6 | import com.mineinabyss.geary.datatypes.Relation 7 | import com.mineinabyss.geary.datatypes.family.family 8 | import com.mineinabyss.geary.engine.ComponentProvider 9 | import com.mineinabyss.geary.engine.archetypes.Archetype 10 | import com.mineinabyss.geary.systems.accessors.Accessor 11 | import com.mineinabyss.geary.systems.accessors.FamilyMatching 12 | import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor 13 | import com.mineinabyss.geary.systems.query.Query 14 | 15 | class RelationsAccessor( 16 | val comp: ComponentProvider, 17 | override val originalAccessor: Accessor?, 18 | val kind: ComponentId, 19 | val target: EntityId, 20 | ) : ReadOnlyAccessor>, FamilyMatching { 21 | override val family = family { hasRelation(kind, target) } 22 | 23 | private var cachedRelations = emptyList() 24 | private var cachedArchetype: Archetype? = null 25 | 26 | @OptIn(UnsafeAccessors::class) 27 | override fun get(query: Query): List { 28 | val archetype = query.archetype 29 | if (archetype != cachedArchetype) { 30 | cachedArchetype = archetype 31 | cachedRelations = archetype.getRelations(kind, target) 32 | } 33 | 34 | return cachedRelations 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/builders/SystemBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.builders 2 | 3 | import com.mineinabyss.geary.engine.Pipeline 4 | import com.mineinabyss.geary.systems.System 5 | import com.mineinabyss.geary.systems.TrackedSystem 6 | import com.mineinabyss.geary.systems.query.CachedQuery 7 | import com.mineinabyss.geary.systems.query.Query 8 | import kotlin.time.Duration 9 | 10 | data class SystemBuilder( 11 | val name: String, 12 | val query: T, 13 | val pipeline: Pipeline, 14 | val interval: Duration? = null 15 | ) { 16 | fun named(name: String): SystemBuilder { 17 | return copy(name = name) 18 | } 19 | 20 | fun every(interval: Duration): SystemBuilder { 21 | return copy(interval = interval) 22 | } 23 | 24 | inline fun exec(crossinline run: (T) -> Unit): TrackedSystem<*> { 25 | val onTick: CachedQuery.() -> Unit = { forEach { run(it) } } 26 | val system = System(name, query, onTick, interval) 27 | return pipeline.addSystem(system) 28 | } 29 | 30 | inline fun defer(crossinline run: (T) -> R): DeferredSystemBuilder { 31 | val onTick: CachedQuery.() -> List> = { 32 | mapWithEntity { run(it) } 33 | } 34 | val system = DeferredSystemBuilder(this, onTick) 35 | return system 36 | } 37 | 38 | fun execOnAll(run: CachedQuery.() -> Unit): TrackedSystem<*> { 39 | val system = System(name, query, run, interval) 40 | return pipeline.addSystem(system) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/backend/core/index.md: -------------------------------------------------------------------------------- 1 | # Core 2 | 3 | We use an archetypal engine, heavily inspired by flecs (especially our concept of relations.) Ensure you read the Geary guide before starting this, as I use many terms and concepts from there. 4 | 5 | The core functionality we want from ECS is adding and removing data on entities, and quickly querying entities to work with our data. 6 | 7 | ## Archetypes 8 | 9 | We group entities with an identical set of components into one "archetype." The reasoning is that most situations with really high entity counts have many similar entities (imagine a set of 1 million particles.) We can also memoize some expensive operations using archetypes, so they also act as a sort of cache layer. 10 | 11 | Notice that there are downsides too, since adding and removing components on an entity requires moving a bunch of data from one archetype to another (this isn't an issue when just updating data though, which is a relief!) Generally archetypes are said to maximize iteration performance, at the cost of slower data modification. 12 | 13 | ## Querying data 14 | 15 | The core part of writing fast queries is efficiently finding all the archetypes that match a family of components. We can cache a lot of this work for queries that run often (ex. in repeating systems) at the cost of memory usage. 16 | 17 | Once we get all matching archetypes, we also need to efficiently read data for each entity, which traditionally comes from tightly packing it in memory (but this is difficult on the JVM.) 18 | 19 | In the rest of this section, we'll explore the specifics of how Geary modifies entity data, and queries it. 20 | -------------------------------------------------------------------------------- /addons/geary-uuid/src/com/mineinabyss/geary/uuid/UUID2GearyMap.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.uuid 2 | 3 | import com.mineinabyss.geary.datatypes.EntityId 4 | import kotlinx.atomicfu.locks.SynchronizedObject 5 | import kotlinx.atomicfu.locks.synchronized 6 | import kotlin.uuid.Uuid 7 | 8 | interface UUID2GearyMap { 9 | operator fun get(uuid: Uuid): EntityId? 10 | 11 | operator fun set(uuid: Uuid, entity: EntityId): EntityId? 12 | 13 | operator fun contains(uuid: Uuid): Boolean 14 | fun remove(uuid: Uuid): EntityId? 15 | } 16 | 17 | class SimpleUUID2GearyMap : UUID2GearyMap { 18 | private val map = mutableMapOf() 19 | 20 | override operator fun get(uuid: Uuid): EntityId? = 21 | map[uuid]?.toULong() 22 | 23 | override operator fun set(uuid: Uuid, entity: EntityId): EntityId? = 24 | map.put(uuid, entity.toLong())?.toULong() 25 | 26 | override operator fun contains(uuid: Uuid): Boolean = map.containsKey(uuid) 27 | 28 | override fun remove(uuid: Uuid): EntityId? = 29 | map.remove(uuid)?.toULong() 30 | } 31 | 32 | 33 | class SynchronizedUUID2GearyMap : UUID2GearyMap { 34 | private val unsafe = SimpleUUID2GearyMap() 35 | private val lock = SynchronizedObject() 36 | 37 | override fun get(uuid: Uuid): EntityId? = synchronized(lock) { unsafe[uuid] } 38 | override fun set(uuid: Uuid, entity: EntityId): EntityId? = synchronized(lock) { unsafe.set(uuid, entity) } 39 | override fun contains(uuid: Uuid): Boolean = synchronized(lock) { unsafe.contains(uuid) } 40 | override fun remove(uuid: Uuid): EntityId? = synchronized(lock) { unsafe.remove(uuid) } 41 | } 42 | -------------------------------------------------------------------------------- /docs/guide/setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | Geary uses an addon system to let you load only what you need. 4 | 5 | ## Gradle setup 6 | 7 | Add the dependencies you want, namely `geary-core` and any addons that can be found [here](https://github.com/MineInAbyss/Geary/tree/master/addons). 8 | 9 | ```kotlin 10 | repositories { 11 | maven("https://repo.mineinabyss.com/releases") 12 | } 13 | 14 | dependencies { 15 | val gearyVersion = "x.y.z" 16 | implementation("com.mineinabyss:geary-core:$gearyVersion") 17 | implementation("com.mineinabyss:geary-:$gearyVersion") 18 | } 19 | ``` 20 | 21 | ## Initialize Geary 22 | 23 | Geary provides a helper DSL to get set up, choose an engine implementation, and install your chosen addons. 24 | 25 | ```kotlin 26 | geary(ArchetypeEngineModule()) { 27 | 28 | // Install addons without configuration 29 | 30 | install(UUIDTracking) 31 | 32 | // Some addons provide their own DSL. It will install the addon if it's not already installed 33 | 34 | serialization { 35 | format("yml", ::YamlFormat) 36 | 37 | components { 38 | component(UUID::class, UUIDSerializer.withSerialName("geary:uuid")) 39 | } 40 | } 41 | 42 | autoscan(classLoader, "com.mineinabyss.geary") { 43 | components() 44 | } 45 | }.start() 46 | // Optionally delay calling start so other parts of the code can call .configure for extra configuration 47 | ``` 48 | 49 | ## Testing 50 | 51 | Geary provides a testing module which does not start any pipeline tasks automatically. You can also extend `GearyTest` from the `geary-test` module to have an isolated world created per test. 52 | -------------------------------------------------------------------------------- /docs/guide/listeners.md: -------------------------------------------------------------------------------- 1 | # Listeners 2 | 3 | - Listeners use accessors like Queries, but are not iterators. 4 | - Instead, they define `handlers` which run when an event has been called on an entity that matches the requested data. 5 | 6 | # Creating a listener 7 | 8 | As usual, extend `GearyListener` and add accessors: 9 | 10 | ```kotlin 11 | class ExplosionBehaviour : GearyListener() { 12 | private val ResultScope.explosionData by get() 13 | ... 14 | ``` 15 | 16 | # Add handlers 17 | 18 | Handlers are added by overriding the `GearyHandlerScope.init()` function. 19 | 20 | This function gets called on any matched archetype. It contains info regarding that archetype as well as extension functions to make listening to events easier. 21 | 22 | You may use `on{ event -> ... }` to create a new handler. This handler puts you in a `ResultScope` and passes along an `event: EventType`. 23 | 24 | Search for extension functions before using this function, since you may accidentally use events that are not handled in Geary at all (ex regular Bukkit events.) 25 | 26 | Here's a potential example from Minecraft: 27 | ```kotlin 28 | ... 29 | 30 | override fun GearyHandlerScope.init() { 31 | onItemLeftClick { event -> 32 | event.player.location.explode(explosionData) 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | # Useful events 39 | 40 | - `onComponentAdd` - Fires as soon as all requested components are present on an entity. 41 | 42 | - `onComponentRemove` - Not yet implemented, but will be similar. 43 | 44 | - `ItemActions` - Contains many item-related ones provided by Looty. 45 | 46 | - More related with Bukkit events to come... 47 | -------------------------------------------------------------------------------- /docs/guide/persisting-data.md: -------------------------------------------------------------------------------- 1 | # Persisting data 2 | 3 | - Geary works closely with [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) to allow components to be encoded, stored, and persisted. 4 | - Serializable components can use `setPersisting`. 5 | - Use autoscanner to register serializable components for you. 6 | 7 | # Uses 8 | 9 | For Minecraft, we persist using the binary format CBOR, as well as YAML for prefabs defined in config files. 10 | 11 | Use `entity.setPersisting` with a serializable component to set and persist it. 12 | 13 | # Making your data serializable 14 | 15 | Components don't need to be serializable, but you should always try to make yours serializable. 16 | 17 | Be sure to read more about ktx.serialization, but here's a quick explanation to get you started. 18 | 19 | - Annotate a class as `@Serializable` and `@SerialName("yournamespace:component_name")`. 20 | - Properties inside will be serialized if they all have a serializer. 21 | - Use `@Transient` to avoid serializing a property. 22 | 23 | ## Polymorphic serialization 24 | 25 | Since components can be Any object, when you want to serialize any component, use `@Polymorphic GearyComponent`, ex: 26 | 27 | ```kotlin 28 | @Serializable 29 | @SerialName(...) 30 | class SomeData( 31 | val components: List<@Polymorphic GearyComponent> 32 | ) 33 | ``` 34 | 35 | # Autoscanning 36 | 37 | Within the Geary extension DSL, you can ask to autoscan and register components. This will allow them to be used with any of the formats stored in the `Formats` singleton. 38 | 39 | The autoscanner will look through classes at runtime and automatically register all components annotated with `@AutoscanComponent`. 40 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/queries/QueryForEachMutatingTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.queries 2 | 3 | import com.mineinabyss.geary.annotations.optin.UnsafeAccessors 4 | import com.mineinabyss.geary.helpers.entity 5 | import com.mineinabyss.geary.systems.query.query 6 | import com.mineinabyss.geary.test.GearyTest 7 | import io.kotest.assertions.throwables.shouldThrowAny 8 | import io.kotest.matchers.collections.shouldBeEmpty 9 | import io.kotest.matchers.collections.shouldHaveSize 10 | import org.junit.jupiter.api.BeforeEach 11 | import kotlin.test.Test 12 | 13 | class QueryForEachMutatingTest : GearyTest() { 14 | @BeforeEach 15 | fun setup() { 16 | resetEngine() 17 | repeat(10) { 18 | entity { set(it) } 19 | } 20 | } 21 | 22 | @Test 23 | fun `forEach should fail when modifying unsafeEntity's archetype`() { 24 | val query = queryManager.trackQuery(query()) 25 | shouldThrowAny { 26 | @OptIn(UnsafeAccessors::class) 27 | query.forEach { 28 | it.unsafeEntity.toGeary().remove() 29 | } 30 | } 31 | } 32 | 33 | @Test 34 | fun `forEachEntity should allow modifying archetypes while iterating`() { 35 | val query = queryManager.trackQuery(query()) 36 | val postQuery = queryManager.trackQuery(query()) 37 | 38 | @OptIn(UnsafeAccessors::class) 39 | query.forEachMutating { entity, (integer) -> 40 | entity.remove() 41 | entity.set(integer.toLong()) 42 | } 43 | 44 | query.entities().shouldBeEmpty() 45 | postQuery.entities().shouldHaveSize(10) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /geary-benchmarks/src/com/mineinabyss/geary/benchmarks/misc/ComponentIdTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.benchmarks.misc 2 | 3 | import com.mineinabyss.geary.benchmarks.helpers.GearyBenchmark 4 | import co.touchlab.kermit.Logger 5 | import co.touchlab.kermit.Severity 6 | import com.mineinabyss.geary.benchmarks.helpers.* 7 | import com.mineinabyss.geary.helpers.componentId 8 | import com.mineinabyss.geary.modules.TestEngineModule 9 | import com.mineinabyss.geary.modules.geary 10 | import org.openjdk.jmh.annotations.Benchmark 11 | import org.openjdk.jmh.annotations.Scope 12 | import org.openjdk.jmh.annotations.Setup 13 | import org.openjdk.jmh.annotations.State 14 | import kotlin.reflect.typeOf 15 | 16 | @State(Scope.Benchmark) 17 | class ComponentIdTest : GearyBenchmark() { 18 | 19 | @Setup 20 | fun setup() { 21 | Logger.setMinSeverity(Severity.Warn) 22 | geary(TestEngineModule) 23 | } 24 | 25 | @Benchmark 26 | fun getKType() { 27 | repeat(tenMil) { 28 | typeOf() 29 | } 30 | } 31 | 32 | @Benchmark 33 | fun getKClass() { 34 | repeat(tenMil) { 35 | typeOf().classifier 36 | } 37 | } 38 | 39 | @Benchmark 40 | fun componentIdFor6Comp() { 41 | repeat(tenMil) { 42 | componentId() 43 | componentId() 44 | componentId() 45 | componentId() 46 | componentId() 47 | componentId() 48 | } 49 | } 50 | } 51 | 52 | fun main() { 53 | geary(TestEngineModule) 54 | ComponentIdTest().apply { 55 | setup() 56 | repeat(10) { 57 | componentIdFor6Comp() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /addons/geary-prefabs/test@jvm/com/mineinabyss/geary/prefabs/GearyEntityComponentIdSerializerTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | import com.mineinabyss.geary.modules.TestEngineModule 4 | import com.mineinabyss.geary.modules.geary 5 | import com.mineinabyss.geary.serialization.SerializableComponents 6 | import com.mineinabyss.geary.serialization.formats.YamlFormat 7 | import com.mineinabyss.geary.serialization.serialization 8 | import com.mineinabyss.geary.serialization.serializers.GearyEntitySerializer 9 | import com.mineinabyss.geary.test.GearyTest 10 | import io.kotest.matchers.collections.shouldContainExactly 11 | import kotlinx.serialization.SerialName 12 | import kotlinx.serialization.Serializable 13 | import org.junit.jupiter.api.Test 14 | import org.junit.jupiter.api.TestInstance 15 | 16 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 17 | class GearyEntityComponentIdSerializerTest : GearyTest() { 18 | override fun setupGeary() = geary(TestEngineModule) { 19 | serialization { 20 | components { 21 | component(A.serializer()) 22 | } 23 | } 24 | } 25 | 26 | @Serializable 27 | @SerialName("test:thing.a") 28 | object A 29 | 30 | @Test 31 | fun `GearyEntitySerializer should deserialize to entity correctly`() { 32 | // arrange 33 | val format = YamlFormat(getAddon(SerializableComponents).serializers.module) 34 | val file = 35 | """ 36 | test:thing.a: {} 37 | """.trimIndent() 38 | 39 | // act 40 | val entity = 41 | format.decodeFromString(GearyEntitySerializer(), file) 42 | 43 | // assert 44 | entity.getAll() shouldContainExactly listOf(A) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /geary-benchmarks/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import kotlinx.benchmark.gradle.JvmBenchmarkTarget 2 | import org.jetbrains.kotlin.allopen.gradle.AllOpenExtension 3 | 4 | plugins { 5 | id(idofrontLibs.plugins.mia.kotlin.jvm.get().pluginId) 6 | id("org.jetbrains.kotlinx.benchmark") version "0.4.9" 7 | kotlin("plugin.allopen") version idofrontLibs.versions.kotlin.get() 8 | } 9 | 10 | configure { 11 | annotation("org.openjdk.jmh.annotations.State") 12 | } 13 | 14 | dependencies { 15 | implementation(project(":geary-core")) 16 | implementation(libs.kotlinx.benchmark.runtime) 17 | } 18 | 19 | benchmark { 20 | configurations { 21 | named("main") { 22 | exclude("jvmTesting") 23 | warmups = 3 24 | iterations = 3 25 | iterationTime = 5 26 | iterationTimeUnit = "sec" 27 | } 28 | 29 | create("fast") { 30 | exclude("jvmTesting") 31 | warmups = 1 32 | iterations = 1 33 | iterationTime = 3 34 | iterationTimeUnit = "sec" 35 | } 36 | 37 | create("fastest") { 38 | exclude("jvmTesting") 39 | warmups = 1 40 | iterations = 1 41 | iterationTime = 3 42 | iterationTimeUnit = "sec" 43 | } 44 | 45 | create("specific") { 46 | include("Unpack6") 47 | warmups = 3 48 | iterations = 3 49 | iterationTime = 5 50 | iterationTimeUnit = "sec" 51 | } 52 | } 53 | targets { 54 | register("main") { 55 | this as JvmBenchmarkTarget 56 | jmhVersion = "1.21" 57 | } 58 | } 59 | } 60 | 61 | sourceSets.main { 62 | kotlin.srcDirs("src") 63 | } 64 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/TypeRoles.kt: -------------------------------------------------------------------------------- 1 | // Some of these run often enough and are stable enough to justify inlining. 2 | @file:Suppress("NOTHING_TO_INLINE") 3 | 4 | package com.mineinabyss.geary.datatypes 5 | 6 | //TODO Currently avoiding using consts because it's a breaking change, check if this is actually worth the perf. 7 | //const val NO_ROLE: ULong = 0uL 8 | //const val RELATION: ULong = 0x8000000000000000uL // 1 shl 63 9 | //const val HOLDS_DATA: ULong = 0x4000000000000000uL // 1 shl 62 10 | val NO_ROLE: ULong = 0uL 11 | val RELATION: ULong = 1uL shl 63 12 | val HOLDS_DATA: ULong = 1uL shl 62 13 | //4 14 | //5 15 | //5 16 | //6 17 | //7 18 | //8 19 | //No more bits reserved 20 | 21 | const val TYPE_ROLES_MASK: ULong = 0xFF00000000000000uL 22 | const val ENTITY_MASK: ULong = 0x00FFFFFFFFFFFFFFuL 23 | const val RELATION_KIND_MASK: ULong = 0xFFFFFFFF00000000uL 24 | const val RELATION_TARGET_MASK: ULong = 0x00000000FFFFFFFFuL 25 | 26 | inline fun ComponentId.isRelation(): Boolean = this.hasRole(RELATION) 27 | inline fun ComponentId.holdsData(): Boolean = this.hasRole(HOLDS_DATA) 28 | 29 | inline fun ComponentId.hasRole(role: ULong): Boolean = this and role != 0uL 30 | inline fun Relation.hasRole(role: ULong): Boolean = id.hasRole(role) 31 | 32 | inline fun ComponentId.withRole(role: ULong): ULong = this or role 33 | inline fun Relation.withRole(role: ULong): Relation = Relation.of(id.withRole(role)) 34 | 35 | inline fun ComponentId.withoutRole(role: ULong): ULong = this and role.inv() 36 | inline fun Relation.withoutRole(role: ULong): Relation = Relation.of(id.withoutRole(role)) 37 | 38 | inline fun ComponentId.withInvertedRole(role: ULong): ULong = this xor role 39 | inline fun Relation.withInvertedRole(role: ULong): Relation = Relation.of(id.withInvertedRole(role)) 40 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/archetypes/ComponentAsEntityProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine.archetypes 2 | 3 | import co.touchlab.kermit.Logger 4 | import com.mineinabyss.geary.components.ComponentInfo 5 | import com.mineinabyss.geary.components.ReservedComponents 6 | import com.mineinabyss.geary.datatypes.ComponentId 7 | import com.mineinabyss.geary.engine.ComponentProvider 8 | import com.mineinabyss.geary.engine.EntityProvider 9 | import kotlin.reflect.KClassifier 10 | 11 | class ComponentAsEntityProvider( 12 | val entityProvider: EntityProvider, 13 | val logger: Logger, 14 | ) : ComponentProvider { 15 | private val classToComponentMap = mutableMapOf() 16 | // private val classToComponentMapLock = Synchronizable() TODO async support necessary? 17 | 18 | init { 19 | createReservedComponents() 20 | } 21 | 22 | override fun getOrRegisterComponentIdForClass(kClass: KClassifier): ComponentId { 23 | val id = classToComponentMap.getOrElse(kClass) { 24 | return registerComponentIdForClass(kClass) 25 | } 26 | return id.toULong() 27 | } 28 | 29 | private fun registerComponentIdForClass(kClass: KClassifier): ComponentId { 30 | logger.v("Registering new component: $kClass") 31 | val compEntity = entityProvider.create() 32 | // compEntity.set(ComponentInfo(kClass), noEvent = true) 33 | classToComponentMap[kClass] = compEntity.toLong() 34 | return compEntity 35 | } 36 | 37 | private fun createReservedComponents() { 38 | logger.v("Creating reserved components") 39 | ReservedComponents.reservedComponents.forEach { (kClass, id) -> 40 | classToComponentMap[kClass] = id.toLong() 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/systems/FamilyMatchingTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems 2 | 3 | import com.mineinabyss.geary.datatypes.HOLDS_DATA 4 | import com.mineinabyss.geary.helpers.componentId 5 | import com.mineinabyss.geary.helpers.entity 6 | import com.mineinabyss.geary.test.GearyTest 7 | import com.mineinabyss.geary.systems.query.Query 8 | import io.kotest.matchers.collections.shouldContain 9 | import io.kotest.matchers.collections.shouldContainAll 10 | import io.kotest.matchers.shouldBe 11 | import org.junit.jupiter.api.Test 12 | 13 | class FamilyMatchingTest : GearyTest() { 14 | val stringId = componentId() or HOLDS_DATA 15 | val intId = componentId() 16 | 17 | val system = system(object : Query(this) { 18 | val string by get() 19 | override fun ensure() = this { has() } 20 | }).defer { it.string }.onFinish { data, entity -> 21 | data shouldBe entity.get() 22 | entity.has() shouldBe true 23 | } 24 | 25 | val root = rootArchetype 26 | val correctArchetype = root + stringId + intId 27 | 28 | @Test 29 | fun `archetypes have been matched correctly`() { 30 | system.runner.matchedArchetypes shouldContain correctArchetype 31 | } 32 | 33 | @Test 34 | fun `get entities matching family`() { 35 | val entity = entity { 36 | set("Test") 37 | add() 38 | } 39 | val entity2 = entity { 40 | set("Test") 41 | set(1) 42 | } 43 | val entities = findEntities(system.runner.family) 44 | entities.shouldContainAll(entity, entity2) 45 | } 46 | 47 | @Test 48 | fun `accessors in system correctly read data`() { 49 | system.tick() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/serializers/InnerSerializer.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.serializers 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.descriptors.PrimitiveKind 5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | import kotlinx.serialization.encoding.Decoder 8 | import kotlinx.serialization.encoding.Encoder 9 | 10 | abstract class InnerSerializer( 11 | val serialName: String, 12 | val inner: KSerializer, 13 | val transform: Decoder.(I) -> O, 14 | val inverseTransform: (O) -> I, 15 | ) : KSerializer { 16 | override val descriptor = 17 | if (inner.descriptor.kind is PrimitiveKind) 18 | PrimitiveSerialDescriptor(serialName, inner.descriptor.kind as PrimitiveKind) 19 | else SerialDescriptor(serialName, inner.descriptor) 20 | 21 | override fun deserialize(decoder: Decoder): O { 22 | return transform(decoder, inner.deserialize(decoder)) 23 | } 24 | 25 | override fun serialize(encoder: Encoder, value: O) { 26 | inner.serialize(encoder, inverseTransform(value)) 27 | } 28 | } 29 | 30 | // TODO this causes a compiler error right now 31 | //inline fun innerSerializer( 32 | // serialName: String, 33 | // transform: Decoder.(In) -> Out, 34 | // inverseTransform: (Out) -> In, 35 | // inner: KSerializer = serializer(), 36 | //): InnerSerializer { 37 | // return object : InnerSerializer( 38 | // serialName = serialName,// ?: inner.descriptor.serialName, 39 | // inner = inner, 40 | // transform = transform, 41 | // inverseTransform = inverseTransform, 42 | // ) {} 43 | //} 44 | -------------------------------------------------------------------------------- /geary-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(idofrontLibs.plugins.mia.kotlin.multiplatform.get().pluginId) 3 | id(idofrontLibs.plugins.mia.publication.get().pluginId) 4 | alias(idofrontLibs.plugins.kotlinx.serialization) 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | commonMain { 10 | dependencies { 11 | implementation(libs.stately.concurrency) 12 | implementation(libs.androidx.collection) 13 | implementation(idofrontLibs.kotlin.reflect) 14 | 15 | api(libs.koin.core) 16 | api(idofrontLibs.kotlinx.io) 17 | api(idofrontLibs.kermit) 18 | api(idofrontLibs.kotlinx.coroutines) 19 | } 20 | kotlin.setSrcDirs(files("src")) 21 | } 22 | 23 | jvmTest { 24 | dependencies { 25 | implementation(project(":geary-test")) 26 | implementation(kotlin("test")) 27 | implementation(idofrontLibs.kotlinx.coroutines.test) 28 | implementation(idofrontLibs.kotest.assertions) 29 | implementation(idofrontLibs.kotest.property) 30 | implementation(libs.koin.test) 31 | } 32 | kotlin.setSrcDirs(files("test@jvm")) 33 | resources.setSrcDirs(files("resources")) 34 | } 35 | 36 | jvmMain { 37 | dependencies { 38 | implementation(idofrontLibs.kotlinx.serialization.kaml) 39 | implementation(idofrontLibs.fastutil) 40 | implementation(libs.roaringbitmap) 41 | } 42 | kotlin.setSrcDirs(files("src@jvm")) 43 | } 44 | 45 | nativeMain { 46 | kotlin.setSrcDirs(files("src@native")) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /geary-test/src/com/mineinabyss/geary/test/GearyTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.test 2 | 3 | import com.mineinabyss.geary.engine.Engine 4 | import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider 5 | import com.mineinabyss.geary.modules.* 6 | import kotlinx.coroutines.Deferred 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.async 9 | import kotlinx.coroutines.withContext 10 | import org.junit.jupiter.api.AfterAll 11 | import org.junit.jupiter.api.TestInstance 12 | import org.koin.core.KoinApplication 13 | 14 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 15 | abstract class GearyTest : Geary { 16 | private var _application: KoinApplication? = null 17 | override val application get() = _application!! 18 | val rootArchetype get() = get().rootArchetype 19 | 20 | open fun setupGeary() = geary(TestEngineModule) 21 | 22 | init { 23 | startEngine() 24 | } 25 | 26 | fun startEngine(override: UninitializedGearyModule? = null) { 27 | _application = (override ?: setupGeary()).start().application 28 | } 29 | 30 | @AfterAll 31 | fun clearEngine() { 32 | _application = null 33 | } 34 | 35 | /** Recreates the engine. */ 36 | fun resetEngine(override: UninitializedGearyModule? = null) { 37 | clearEngine() 38 | startEngine(override) 39 | } 40 | 41 | companion object { 42 | suspend inline fun concurrentOperation( 43 | times: Int = 10000, 44 | crossinline run: suspend (id: Int) -> T, 45 | ): List> { 46 | return withContext(Dispatchers.Default) { 47 | (0 until times).map { id -> 48 | async { run(id) } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /geary-core/src@jvm/com/mineinabyss/geary/datatypes/BitSet.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | import org.roaringbitmap.IntConsumer 4 | import org.roaringbitmap.RoaringBitmap 5 | 6 | actual class BitSet { 7 | @PublishedApi 8 | internal val inner: RoaringBitmap 9 | 10 | actual constructor() { 11 | inner = RoaringBitmap() 12 | } 13 | 14 | constructor(from: RoaringBitmap) { 15 | inner = from 16 | } 17 | 18 | actual fun isEmpty(): Boolean = inner.isEmpty 19 | 20 | actual operator fun get(index: Int): Boolean = 21 | inner.contains(index) 22 | 23 | actual fun set(index: Int) { 24 | inner.add(index) 25 | } 26 | 27 | actual fun set(from: Int, to: Int) { 28 | inner.add(from, to) 29 | } 30 | 31 | actual fun clear(index: Int) { 32 | inner.remove(index) 33 | } 34 | 35 | actual fun flip(index: Int) { 36 | inner.flip(index) 37 | } 38 | 39 | actual fun and(other: BitSet) { 40 | inner.and(other.inner) 41 | } 42 | 43 | actual fun andNot(other: BitSet) { 44 | inner.andNot(other.inner) 45 | } 46 | 47 | actual fun or(other: BitSet) { 48 | inner.or(other.inner) 49 | } 50 | 51 | actual fun xor(other: BitSet) { 52 | inner.xor(other.inner) 53 | } 54 | 55 | actual fun clear() { 56 | inner.clear() 57 | } 58 | 59 | actual val cardinality: Int 60 | get() = inner.cardinality 61 | 62 | 63 | actual inline fun forEachBit(crossinline loop: (Int) -> Unit) { 64 | // Roaring bitsets run into concurrent modification issues where clearing a bit might skip iterating another, 65 | // so we have to clone the set. 66 | inner.clone().forEach(IntConsumer { loop(it) }) 67 | } 68 | 69 | actual fun copy(): BitSet = BitSet(inner.clone()) 70 | } 71 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/formats/Format.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.formats 2 | 3 | import kotlinx.io.Buffer 4 | import kotlinx.io.Sink 5 | import kotlinx.io.Source 6 | import kotlinx.io.files.SystemFileSystem 7 | import kotlinx.io.writeString 8 | import kotlinx.serialization.DeserializationStrategy 9 | import kotlinx.serialization.SerializationStrategy 10 | import kotlinx.serialization.modules.SerializersModule 11 | 12 | interface Format { 13 | val ext: String 14 | 15 | // fun decodeFromString( 16 | // deserializer: DeserializationStrategy, 17 | // string: String, 18 | // overrideSerializersModule: SerializersModule? = null, 19 | // configType: ConfigType = ConfigType.REGULAR, 20 | // ): T 21 | 22 | fun decode( 23 | deserializer: DeserializationStrategy, 24 | source: Source, 25 | overrideSerializersModule: SerializersModule? = null, 26 | configType: ConfigType = ConfigType.REGULAR, 27 | ): T 28 | 29 | fun encode( 30 | serializer: SerializationStrategy, 31 | value: T, 32 | sink: Sink, 33 | overrideSerializersModule: SerializersModule? = null, 34 | configType: ConfigType = ConfigType.REGULAR, 35 | ) 36 | 37 | enum class ConfigType { 38 | REGULAR, 39 | NON_STRICT 40 | } 41 | 42 | fun decodeFromString( 43 | deserializer: DeserializationStrategy, 44 | string: String, 45 | overrideSerializersModule: SerializersModule? = null, 46 | configType: ConfigType = ConfigType.REGULAR, 47 | ): T { 48 | val buffer = Buffer().apply { writeString(string) } 49 | return decode(deserializer, buffer, overrideSerializersModule, configType) 50 | } 51 | } 52 | 53 | 54 | fun main() { 55 | SystemFileSystem 56 | } 57 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/PipelineImpl.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine 2 | 3 | import com.mineinabyss.geary.addons.GearyPhase 4 | import com.mineinabyss.geary.helpers.fastForEach 5 | import com.mineinabyss.geary.modules.geary 6 | import com.mineinabyss.geary.systems.System 7 | import com.mineinabyss.geary.systems.TrackedSystem 8 | import com.mineinabyss.geary.systems.query.Query 9 | 10 | class PipelineImpl( 11 | val queryManager: QueryManager 12 | ) : Pipeline { 13 | private val onSystemAdd = mutableListOf<(System<*>) -> Unit>() 14 | private val repeatingSystems: MutableSet> = mutableSetOf() 15 | 16 | private val scheduled = Array(GearyPhase.entries.size) { mutableListOf<() -> Unit>() } 17 | private var currentPhase = GearyPhase.entries.first() 18 | 19 | override fun runOnOrAfter(phase: GearyPhase, block: () -> Unit) { 20 | if (currentPhase > phase) block() 21 | else scheduled[phase.ordinal].add(block) 22 | } 23 | 24 | override fun onSystemAdd(run: (System<*>) -> Unit) { 25 | onSystemAdd.add(run) 26 | } 27 | 28 | override fun runStartupTasks() { 29 | scheduled.fastForEach { actions -> 30 | actions.fastForEach { it() } 31 | } 32 | } 33 | 34 | override fun addSystem(system: System): TrackedSystem<*> { 35 | onSystemAdd.fastForEach { it(system) } 36 | val runner = queryManager.trackQuery(system.query) 37 | val tracked = TrackedSystem(system, runner) 38 | repeatingSystems.add(tracked) 39 | return TrackedSystem(system, runner) 40 | } 41 | 42 | override fun addSystems(vararg systems: System<*>) { 43 | systems.fastForEach { addSystem(it) } 44 | } 45 | 46 | override fun getRepeatingInExecutionOrder(): Iterable> { 47 | return repeatingSystems 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /addons/geary-prefabs/test@jvm/com/mineinabyss/geary/prefabs/CopyToInstancesTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | import com.mineinabyss.geary.components.relations.NoInherit 4 | import com.mineinabyss.geary.helpers.entity 5 | import com.mineinabyss.geary.modules.TestEngineModule 6 | import com.mineinabyss.geary.modules.geary 7 | import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances 8 | import com.mineinabyss.geary.serialization.getAllPersisting 9 | import com.mineinabyss.geary.serialization.serialization 10 | import com.mineinabyss.geary.test.GearyTest 11 | import io.kotest.assertions.assertSoftly 12 | import io.kotest.matchers.shouldBe 13 | import kotlinx.serialization.builtins.serializer 14 | import org.junit.jupiter.api.Test 15 | 16 | class CopyToInstancesTest : GearyTest() { 17 | override fun setupGeary() = geary(TestEngineModule) { 18 | serialization { 19 | components { 20 | component(String.serializer()) 21 | component(Int.serializer()) 22 | } 23 | } 24 | 25 | install(Prefabs) 26 | } 27 | 28 | @Test 29 | fun `should correctly add temporary and persisting components with CopyToInstances`() { 30 | // arrange 31 | val prefab = entity { 32 | set( 33 | CopyToInstances( 34 | temporary = listOf(42), 35 | persisting = listOf("Hello world"), 36 | ) 37 | ) 38 | addRelation() 39 | } 40 | 41 | // act 42 | val instance = entity { extend(prefab) } 43 | 44 | // assert 45 | assertSoftly(instance) { 46 | get() shouldBe "Hello world" 47 | get() shouldBe 42 48 | getAllPersisting() shouldBe listOf("Hello world") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/serializers/DurationSerializer.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.serializers 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.descriptors.PrimitiveKind 5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | import kotlinx.serialization.encoding.Decoder 8 | import kotlinx.serialization.encoding.Encoder 9 | import kotlin.time.Duration 10 | import kotlin.time.Duration.Companion.days 11 | import kotlin.time.Duration.Companion.hours 12 | import kotlin.time.Duration.Companion.milliseconds 13 | import kotlin.time.Duration.Companion.minutes 14 | import kotlin.time.Duration.Companion.seconds 15 | 16 | internal object DurationSerializer : KSerializer { 17 | override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Time", PrimitiveKind.STRING) 18 | 19 | override fun serialize(encoder: Encoder, value: Duration) = 20 | encoder.encodeString(value.toString()) 21 | 22 | override fun deserialize(decoder: Decoder): Duration { 23 | val string = decoder.decodeString() 24 | return Duration.parseOrNull(string) ?: fromString(decoder.decodeString()) ?: error("Not a valid duration: $string") 25 | } 26 | 27 | private fun fromString(string: String): Duration? { 28 | val splitAt = string.indexOfFirst { it.isLetter() }.takeIf { it > 0 } ?: string.length 29 | val value = string.take(splitAt).toDouble() 30 | return when (string.drop(splitAt)) { 31 | "ms" -> value.milliseconds 32 | "s" -> value.seconds 33 | "m" -> value.minutes 34 | "h" -> value.hours 35 | "d" -> value.days 36 | "w" -> value.days * 7 37 | "mo" -> value.days * 31 38 | else -> null 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/accessors/type/RelationsWithDataAccessor.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.accessors.type 2 | 3 | import com.mineinabyss.geary.annotations.optin.UnsafeAccessors 4 | import com.mineinabyss.geary.datatypes.ComponentId 5 | import com.mineinabyss.geary.datatypes.EntityId 6 | import com.mineinabyss.geary.datatypes.Relation 7 | import com.mineinabyss.geary.datatypes.family.family 8 | import com.mineinabyss.geary.engine.ComponentProvider 9 | import com.mineinabyss.geary.engine.archetypes.Archetype 10 | import com.mineinabyss.geary.systems.accessors.Accessor 11 | import com.mineinabyss.geary.systems.accessors.FamilyMatching 12 | import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor 13 | import com.mineinabyss.geary.systems.accessors.RelationWithData 14 | import com.mineinabyss.geary.systems.query.Query 15 | 16 | @OptIn(UnsafeAccessors::class) 17 | class RelationsWithDataAccessor( 18 | val comp: ComponentProvider, 19 | override val originalAccessor: Accessor?, 20 | val kind: ComponentId, 21 | val target: EntityId, 22 | ) : ReadOnlyAccessor>>, FamilyMatching { 23 | override val family = family { hasRelation(kind, target) } 24 | 25 | private var cachedRelations = emptyList() 26 | private var cachedArchetype: Archetype? = null 27 | 28 | override fun get(query: Query): List> { 29 | val archetype = query.archetype 30 | if (archetype != cachedArchetype) { 31 | cachedArchetype = archetype 32 | cachedRelations = archetype.getRelations(kind, target) 33 | } 34 | 35 | @Suppress("UNCHECKED_CAST") 36 | return archetype.readRelationDataFor( 37 | query.row, 38 | kind, 39 | target, 40 | cachedRelations 41 | ) as List> 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /addons/geary-serialization/src/com/mineinabyss/geary/serialization/serializers/SerializableComponentId.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.serialization.serializers 2 | 3 | import com.mineinabyss.geary.datatypes.Component 4 | import com.mineinabyss.geary.datatypes.ComponentId 5 | import com.mineinabyss.geary.helpers.componentId 6 | import com.mineinabyss.geary.modules.Geary 7 | import com.mineinabyss.geary.serialization.ComponentSerializers 8 | import kotlinx.serialization.Contextual 9 | import kotlinx.serialization.KSerializer 10 | import kotlinx.serialization.descriptors.PrimitiveKind 11 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 12 | import kotlinx.serialization.encoding.Decoder 13 | import kotlinx.serialization.encoding.Encoder 14 | import kotlinx.serialization.modules.SerializersModule 15 | import kotlin.reflect.KClass 16 | 17 | typealias SerializableComponentId = @Contextual ComponentId 18 | 19 | class ComponentIdSerializer( 20 | val componentSerializers: ComponentSerializers, 21 | val world: Geary, 22 | ) : KSerializer { 23 | override val descriptor = PrimitiveSerialDescriptor("EventComponent", PrimitiveKind.STRING) 24 | 25 | private val polymorphicListAsMapSerializer = PolymorphicListAsMapSerializer.ofComponents() 26 | 27 | override fun deserialize(decoder: Decoder): SerializableComponentId { 28 | return world.componentId(getComponent(decoder.decodeString(), decoder.serializersModule)) 29 | } 30 | 31 | override fun serialize(encoder: Encoder, value: SerializableComponentId) { 32 | TODO() 33 | } 34 | 35 | fun getComponent(name: String, module: SerializersModule): KClass { 36 | val namespaces = polymorphicListAsMapSerializer 37 | .getParentConfig(module)?.namespaces 38 | ?: emptyList() 39 | return componentSerializers.getClassFor(name, namespaces) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/helpers/Iteration.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Taken from korlibs KDS, licensed under MIT https://github.com/korlibs/korge/tree/main/korlibs-datastructure 3 | */ 4 | package com.mineinabyss.geary.helpers 5 | 6 | inline fun List.fastForEach(callback: (T) -> Unit) { 7 | var n = 0 8 | while (n < size) callback(this[n++]) 9 | } 10 | 11 | inline fun Array.fastForEach(callback: (T) -> Unit) { 12 | var n = 0 13 | while (n < size) callback(this[n++]) 14 | } 15 | 16 | inline fun IntArray.fastForEach(callback: (Int) -> Unit) { 17 | var n = 0 18 | while (n < size) callback(this[n++]) 19 | } 20 | 21 | inline fun FloatArray.fastForEach(callback: (Float) -> Unit) { 22 | var n = 0 23 | while (n < size) callback(this[n++]) 24 | } 25 | 26 | inline fun DoubleArray.fastForEach(callback: (Double) -> Unit) { 27 | var n = 0 28 | while (n < size) callback(this[n++]) 29 | } 30 | 31 | inline fun List.fastForEachWithIndex(callback: (index: Int, value: T) -> Unit) { 32 | var n = 0 33 | while (n < size) { 34 | callback(n, this[n]) 35 | n++ 36 | } 37 | } 38 | 39 | inline fun Array.fastForEachWithIndex(callback: (index: Int, value: T) -> Unit) { 40 | var n = 0 41 | while (n < size) { 42 | callback(n, this[n]) 43 | n++ 44 | } 45 | } 46 | 47 | inline fun List.fastForEachReverse(callback: (T) -> Unit) { 48 | var n = 0 49 | while (n < size) { 50 | callback(this[size - n - 1]) 51 | n++ 52 | } 53 | } 54 | 55 | inline fun MutableList.fastIterateRemove(callback: (T) -> Boolean): MutableList { 56 | var n = 0 57 | var m = 0 58 | while (n < size) { 59 | if (m >= 0 && m != n) this[m] = this[n] 60 | if (callback(this[n])) m-- 61 | n++ 62 | m++ 63 | } 64 | while (this.size > m) this.removeAt(this.size - 1) 65 | return this 66 | } 67 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/observers/ObserverList.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers 2 | 3 | import androidx.collection.LongSparseArray 4 | import androidx.collection.MutableObjectList 5 | import androidx.collection.mutableObjectListOf 6 | import com.mineinabyss.geary.datatypes.ComponentId 7 | import com.mineinabyss.geary.datatypes.EntityId 8 | import com.mineinabyss.geary.datatypes.getOrPut 9 | import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap 10 | import com.mineinabyss.geary.engine.archetypes.Archetype 11 | import com.mineinabyss.geary.helpers.NO_COMPONENT 12 | 13 | class ObserverList( 14 | val records: ArrayTypeMap, 15 | ) { 16 | val involved2Observer = LongSparseArray>() 17 | 18 | fun add(observer: Observer) { 19 | if (observer.involvedComponents.size == 0) { 20 | involved2Observer.getOrPut(0L) { mutableObjectListOf() }.add(observer) 21 | } else observer.involvedComponents.forEach { componentId -> 22 | involved2Observer.getOrPut(componentId.toLong()) { mutableObjectListOf() }.add(observer) 23 | } 24 | } 25 | 26 | fun remove(observer: Observer) { 27 | if (observer.involvedComponents.size == 0) { 28 | involved2Observer[0L]?.remove(observer) 29 | } else observer.involvedComponents.forEach { componentId -> 30 | involved2Observer[componentId.toLong()]?.remove(observer) 31 | } 32 | } 33 | 34 | inline fun forEach(involvedComp: ComponentId, entity: EntityId, exec: (Observer, Archetype, row: Int) -> Unit) { 35 | involved2Observer[0L]?.forEach { 36 | records.runOn(entity) { archetype, row -> exec(it, archetype, row) } 37 | } 38 | if (involvedComp != NO_COMPONENT) involved2Observer[involvedComp.toLong()]?.forEach { 39 | records.runOn(entity) { archetype, row -> exec(it, archetype, row) } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /addons/geary-prefabs/test@jvm/com/mineinabyss/geary/prefabs/PrefabFromResourcesTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | import com.mineinabyss.geary.modules.TestEngineModule 4 | import com.mineinabyss.geary.modules.geary 5 | import com.mineinabyss.geary.prefabs.PrefabsDSLExtensions.fromJarResourceDirectory 6 | import com.mineinabyss.geary.prefabs.PrefabsDSLExtensions.fromJarResources 7 | import com.mineinabyss.geary.serialization.formats.YamlFormat 8 | import com.mineinabyss.geary.serialization.serialization 9 | import io.kotest.matchers.shouldBe 10 | import io.kotest.matchers.shouldNotBe 11 | import org.junit.jupiter.api.Test 12 | 13 | class PrefabFromResourcesTest { 14 | private fun world() = geary(TestEngineModule) { 15 | serialization { 16 | format("yml", ::YamlFormat) 17 | } 18 | } 19 | 20 | @Test 21 | fun `should load prefabs from resource file`() { 22 | val world = world().configure { 23 | namespace("test") { 24 | prefabs { 25 | fromJarResources(PrefabFromResourcesTest::class, "prefabs/prefabA.yml") 26 | } 27 | } 28 | }.start() 29 | 30 | with(world) { 31 | entityOfOrNull(PrefabKey.of("test:prefabA")) shouldNotBe null 32 | entityOfOrNull(PrefabKey.of("test:prefabB")) shouldBe null 33 | } 34 | } 35 | 36 | @Test 37 | fun `should load prefabs from resources directory`() { 38 | val world = world().configure { 39 | namespace("test") { 40 | prefabs { 41 | fromJarResourceDirectory(PrefabFromResourcesTest::class, "prefabs") 42 | } 43 | } 44 | }.start() 45 | with(world) { 46 | entityOfOrNull(PrefabKey.of("test:prefabA")) shouldNotBe null 47 | entityOfOrNull(PrefabKey.of("test:prefabB")) shouldNotBe null 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine.archetypes 2 | 3 | import androidx.collection.getOrElse 4 | import androidx.collection.set 5 | import co.touchlab.stately.concurrency.Synchronizable 6 | import co.touchlab.stately.concurrency.synchronize 7 | import com.mineinabyss.geary.datatypes.ComponentId 8 | import com.mineinabyss.geary.datatypes.EntityType 9 | import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap 10 | 11 | class SimpleArchetypeProvider( 12 | private val records: ArrayTypeMap, 13 | private val queryManager: ArchetypeQueryManager, 14 | ) : ArchetypeProvider { 15 | override val rootArchetype: Archetype by lazy { 16 | createArchetype(EntityType(), 0).also { 17 | queryManager.registerArchetype(it) 18 | } 19 | } 20 | 21 | private val archetypeWriteLock = Synchronizable() 22 | 23 | 24 | private fun createArchetype(prevNode: Archetype, componentEdge: ComponentId): Archetype { 25 | val arc = createArchetype(prevNode.type.plus(componentEdge), queryManager.archetypeCount) 26 | arc.componentRemoveEdges[componentEdge.toLong()] = prevNode 27 | prevNode.componentAddEdges[componentEdge.toLong()] = arc 28 | queryManager.registerArchetype(arc) 29 | return arc 30 | } 31 | 32 | private fun createArchetype(type: EntityType, id: Int): Archetype { 33 | return Archetype( 34 | type = type, 35 | id = id, 36 | records = records, 37 | archetypeProvider = this, 38 | ) 39 | } 40 | 41 | override fun getArchetype(entityType: EntityType): Archetype = archetypeWriteLock.synchronize { 42 | var node = rootArchetype 43 | entityType.forEach { compId -> 44 | node = node.componentAddEdges.getOrElse(compId.toLong()) { createArchetype(node, compId) } 45 | } 46 | return@synchronize node 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/PrefabKey.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | import com.mineinabyss.geary.datatypes.Entity 4 | import com.mineinabyss.geary.modules.Geary 5 | import com.mineinabyss.geary.prefabs.serializers.PrefabKeySerializer 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Represents a key build from a [namespace] and [key], separated 10 | * by a '`:`' symbol. 11 | */ 12 | @ConsistentCopyVisibility 13 | @Serializable(with = PrefabKeySerializer::class) 14 | // Note: We don't make this a value class since calculating substring is pretty expensive compared to one new object instantiation 15 | data class PrefabKey private constructor(val namespace: String, val key: String) { 16 | val full get() = "$namespace:$key" 17 | 18 | override fun toString(): String = full 19 | 20 | companion object { 21 | /** Creates a key like [of] but returns null when parsing fails. */ 22 | fun ofOrNull(stringKey: String): PrefabKey? = runCatching { of(stringKey) }.getOrNull() 23 | 24 | /** Creates a key from a string with [namespace] and [key] separated by one '`:`' character. */ 25 | fun of(stringKey: String): PrefabKey { 26 | val split = stringKey.split(':') 27 | if (split.size != 2) 28 | error("Malformed prefab key: $stringKey. Must only contain one : that splits namespace and key.") 29 | return PrefabKey(split[0], split[1]) 30 | } 31 | 32 | /** Creates a key from a [namespace] and [name] which must not contain any '`:`' characters. */ 33 | fun of(namespace: String, name: String): PrefabKey = of("$namespace:$name") 34 | } 35 | } 36 | 37 | fun Geary.entityOfOrNull(key: PrefabKey?): Entity? = key?.let { getAddon(Prefabs).manager[key] } 38 | 39 | fun Geary.entityOf(key: PrefabKey): Entity = entityOfOrNull(key) 40 | ?: error("Requested non null prefab entity for key '$key', but it does not exist.") 41 | -------------------------------------------------------------------------------- /addons/geary-actions/src/com/mineinabyss/geary/actions/actions/EnsureAction.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.actions.actions 2 | 3 | import com.mineinabyss.geary.actions.* 4 | import com.mineinabyss.geary.helpers.componentId 5 | import com.mineinabyss.geary.modules.Geary 6 | import com.mineinabyss.geary.serialization.getWorld 7 | import com.mineinabyss.geary.serialization.serializers.InnerSerializer 8 | import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer 9 | import com.mineinabyss.geary.serialization.serializers.SerializedComponents 10 | import kotlinx.serialization.Serializable 11 | import kotlinx.serialization.Transient 12 | 13 | @Serializable(with = EnsureAction.Serializer::class) 14 | class EnsureAction( 15 | world: Geary, 16 | val conditions: SerializedComponents, 17 | ) : Action { 18 | @Transient 19 | private val flat = conditions.map { world.componentId(it::class) to it } 20 | 21 | override fun ActionGroupContext.execute() { 22 | flat.forEach { (id, data) -> 23 | when (data) { 24 | is Condition -> with(data) { 25 | if (!execute()) { 26 | throw ActionsCancelledException() 27 | } 28 | } 29 | 30 | else -> entity?.emit(id, data) //TODO use geary condition system if we get one 31 | } 32 | } 33 | } 34 | 35 | fun conditionsMet(context: ActionGroupContext): Boolean { 36 | try { 37 | execute(context) 38 | } catch (e: ActionsCancelledException) { 39 | return false 40 | } 41 | return true 42 | } 43 | 44 | class Serializer : InnerSerializer( 45 | serialName = "geary:ensure", 46 | inner = PolymorphicListAsMapSerializer.ofComponents(), 47 | inverseTransform = { it.conditions }, 48 | transform = { EnsureAction(serializersModule.getWorld(), it) } 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/PrefabsDSL.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | import com.mineinabyss.geary.addons.Namespaced 4 | import com.mineinabyss.geary.addons.dsl.GearyDSL 5 | import com.mineinabyss.geary.datatypes.GearyEntity 6 | import kotlinx.io.files.Path 7 | import kotlinx.io.files.SystemFileSystem 8 | 9 | @GearyDSL 10 | class PrefabsDSL( 11 | internal val prefabsBuilder: PrefabSources, 12 | internal val namespaced: Namespaced, 13 | ) { 14 | fun create(vararg prefabs: Pair) { 15 | prefabs.forEach { (name, entity) -> 16 | PrefabLoader.markAsPrefab(entity, PrefabKey.of(namespaced.namespace, name)) 17 | } 18 | } 19 | 20 | /** Loads prefab entities from all files inside a [directory][from], into a given [namespace] */ 21 | fun fromFiles( 22 | vararg from: Path, 23 | ) { 24 | prefabsBuilder.paths.add( 25 | PrefabPath(namespaced.namespace, paths = { from.asSequence() }) 26 | ) 27 | } 28 | 29 | fun fromDirectory(folder: Path) { 30 | prefabsBuilder.paths.add( 31 | PrefabPath(namespaced.namespace, paths = { walkFolder(folder) }) 32 | ) 33 | } 34 | 35 | fun fromSources(vararg sources: PrefabSource) { 36 | prefabsBuilder.paths.add( 37 | PrefabPath(namespaced.namespace, sources = { sources.asSequence() }) 38 | ) 39 | } 40 | 41 | private fun walkFolder(folder: Path): Sequence = sequence { 42 | val fileSystem = SystemFileSystem 43 | val stack = ArrayDeque() 44 | stack.add(folder) 45 | 46 | while (stack.isNotEmpty()) { 47 | val current = stack.removeLast() 48 | if (fileSystem.metadataOrNull(current)?.isDirectory == true) { 49 | fileSystem.list(current).forEach { stack.add(it) } 50 | } else { 51 | yield(current) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/modules/GearySetup.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.modules 2 | 3 | import co.touchlab.kermit.Severity 4 | import com.mineinabyss.geary.addons.Namespaced 5 | import com.mineinabyss.geary.addons.dsl.Addon 6 | import com.mineinabyss.geary.addons.dsl.AddonSetup 7 | import com.mineinabyss.geary.addons.dsl.createAddon 8 | import kotlinx.coroutines.delay 9 | import kotlinx.coroutines.launch 10 | import org.koin.core.KoinApplication 11 | import kotlin.coroutines.CoroutineContext 12 | import kotlin.coroutines.EmptyCoroutineContext 13 | import kotlin.time.Duration 14 | 15 | /** 16 | * Represents a Geary engine whose dependencies have been created in a [GearyModule] and is ready to have addons 17 | * installed. Load phases are accessible here and will be called once start gets called. 18 | */ 19 | class GearySetup( 20 | val application: KoinApplication, 21 | ) { 22 | val geary = Geary(application) 23 | val logger get() = geary.logger 24 | 25 | inline fun , Conf> install(addon: T, configure: Conf.() -> Unit = {}): Conf { 26 | geary.addons.getOrPut(this, addon).apply { config.configure() } 27 | return geary.addons.getConfig(addon) 28 | } 29 | 30 | inline fun install(name: String, crossinline init: AddonSetup.() -> Unit) { 31 | install(createAddon(name) { 32 | init() 33 | }) 34 | } 35 | 36 | fun namespace(namespace: String, configure: Namespaced.() -> Unit) { 37 | Namespaced(namespace, this).configure() 38 | } 39 | 40 | fun loggerSeverity(severity: Severity) { 41 | logger.mutableConfig.minSeverity = severity 42 | } 43 | 44 | fun scheduleTicking( 45 | every: Duration, 46 | context: CoroutineContext = EmptyCoroutineContext, 47 | ) { 48 | geary.engine.mainScope.launch(context) { 49 | while (true) { 50 | geary.engine.tick() 51 | delay(every) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /addons/geary-prefabs/test@jvm/com/mineinabyss/geary/prefabs/ComponentIdSerializerTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs 2 | 3 | import com.mineinabyss.geary.serialization.formats.YamlFormat 4 | import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer 5 | import io.kotest.matchers.shouldBe 6 | import kotlinx.serialization.Polymorphic 7 | import kotlinx.serialization.PolymorphicSerializer 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | import kotlinx.serialization.modules.SerializersModule 11 | import kotlinx.serialization.modules.polymorphic 12 | import kotlinx.serialization.modules.subclass 13 | import org.junit.jupiter.api.Test 14 | import org.junit.jupiter.api.TestInstance 15 | 16 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 17 | class ComponentIdSerializerTest { 18 | interface Components 19 | 20 | @SerialName("test:thing.a") 21 | @Serializable 22 | object A : Components 23 | 24 | @SerialName("test:thing.b") 25 | @Serializable 26 | object B : Components 27 | 28 | @SerialName("test:subserializers") 29 | @Serializable 30 | data class SubSerializers( 31 | val components: @Serializable(with = PolymorphicListAsMapSerializer::class) List<@Polymorphic Components> 32 | ) : Components 33 | 34 | val format = YamlFormat( 35 | SerializersModule { 36 | polymorphic(Components::class) { 37 | subclass(A::class) 38 | subclass(B::class) 39 | subclass(SubSerializers::class) 40 | } 41 | }) 42 | 43 | val mapSerializer = PolymorphicListAsMapSerializer(PolymorphicSerializer(Components::class)) 44 | 45 | @Test 46 | fun `should serialize via @Serializable`() { 47 | val file = 48 | """ 49 | test:thing.a: {} 50 | test:thing.b: {} 51 | """.trimIndent() 52 | format.decodeFromString(mapSerializer, file) shouldBe listOf(A, B) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/engine/archetypes/ArchetypeEngine.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.engine.archetypes 2 | 3 | import co.touchlab.kermit.Logger 4 | import com.mineinabyss.geary.datatypes.EntityType 5 | import com.mineinabyss.geary.engine.Pipeline 6 | import com.mineinabyss.geary.engine.TickingEngine 7 | import com.mineinabyss.geary.helpers.fastForEach 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlin.coroutines.CoroutineContext 10 | import kotlin.time.Duration 11 | 12 | /** 13 | * The default implementation of Geary's Engine. 14 | * 15 | * This engine uses [Archetype]s. Each component is an entity itself with an id associated with it. 16 | * We keep track of each entity's components in the form of its [EntityType] stored in the [records]. 17 | * 18 | * Learn more [here](https://github.com/MineInAbyss/Geary/wiki/Basic-ECS-engine-architecture). 19 | */ 20 | open class ArchetypeEngine( 21 | private val pipeline: Pipeline, 22 | private val logger: Logger, 23 | override val tickDuration: Duration, 24 | private val coroutineContext: () -> CoroutineContext, 25 | ) : TickingEngine() { 26 | override val mainScope by lazy { CoroutineScope(coroutineContext()) } 27 | private var currentTick = 0L 28 | 29 | override fun tick() { 30 | // Create a job but don't start it 31 | pipeline.getRepeatingInExecutionOrder() 32 | .filter { 33 | it.system.interval == null 34 | || (currentTick % (it.system.interval / tickDuration).toInt().coerceAtLeast(1) == 0L) 35 | } 36 | .also { logger.v { "Ticking engine with systems $it" } } 37 | .fastForEach { system -> 38 | runCatching { 39 | system.tick() 40 | }.onFailure { 41 | logger.e { "Error while running system ${system.system.name}" } 42 | it.printStackTrace() 43 | } 44 | } 45 | currentTick++ 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /docs/guide/systems/families.md: -------------------------------------------------------------------------------- 1 | # Defining families 2 | 3 | Families have a counterpart for the following entity operations: `has`, `hasSet`, and `hasRelation` which match against all entities where those operations would return `#!kotlin true`. 4 | 5 | These operations be joined by three connectives, `and, not, or`. 6 | 7 | ## Usage 8 | 9 | 10 | Families don't inline connectives (ex. `A or B`) since we often want to match many components at once. Thus, we use a tree structure. 11 | 12 | Consider three components, `A, B, C`, let's try to build some families from them. 13 | 14 | === ":octicons-file-code-16: A and B" 15 | 16 | ```kotlin 17 | family { 18 | and { 19 | has() 20 | has() 21 | } 22 | } 23 | ``` 24 | Families default to the and selector, so this is equivalent to the following: 25 | 26 | ```kotlin 27 | family { 28 | has() 29 | has() 30 | } 31 | ``` 32 | 33 | === ":octicons-file-code-16: A or B or C" 34 | 35 | ```kotlin 36 | family { 37 | or { 38 | has() 39 | has() 40 | has() 41 | } 42 | } 43 | ``` 44 | 45 | === ":octicons-file-code-16: (A or B) and not C" 46 | 47 | ```kotlin 48 | family { 49 | or { 50 | has() 51 | has() 52 | } 53 | not { 54 | has() 55 | } 56 | } 57 | ``` 58 | 59 | === ":octicons-file-code-16: (A or B) and not (child of C)" 60 | 61 | ```kotlin 62 | family { 63 | or { 64 | has() 65 | has() 66 | } 67 | not { 68 | and { 69 | hasRelation() 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | ## Getting matched entities 76 | 77 | Once created, a family can check if an entity matches it with `#!kotlin entity in family // Boolean`. More importantly, we can now use them in our systems for fast pattern matching in queries. 78 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/systems/query/QueriedEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.systems.query 2 | 3 | import com.mineinabyss.geary.annotations.optin.UnsafeAccessors 4 | import com.mineinabyss.geary.datatypes.EntityId 5 | import com.mineinabyss.geary.datatypes.GearyEntity 6 | import com.mineinabyss.geary.datatypes.family.Family 7 | import com.mineinabyss.geary.datatypes.family.family 8 | import com.mineinabyss.geary.engine.archetypes.Archetype 9 | import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider 10 | import com.mineinabyss.geary.modules.Geary 11 | import com.mineinabyss.geary.modules.get 12 | import com.mineinabyss.geary.systems.accessors.Accessor 13 | import com.mineinabyss.geary.systems.accessors.AccessorOperations 14 | import com.mineinabyss.geary.systems.accessors.FamilyMatching 15 | import kotlin.jvm.JvmField 16 | 17 | open class QueriedEntity( 18 | final override val world: Geary, 19 | final override val cacheAccessors: Boolean, 20 | ) : AccessorOperations(), Geary by world { 21 | @PublishedApi 22 | @UnsafeAccessors 23 | @JvmField 24 | internal var archetype = world.get().rootArchetype 25 | 26 | internal val extraFamilies: MutableList = mutableListOf() 27 | 28 | internal val props: MutableMap = mutableMapOf() 29 | 30 | fun buildFamily(): Family.Selector.And = family { 31 | accessors 32 | .filterIsInstance() 33 | .mapNotNull { it.family } 34 | .union(extraFamilies) 35 | .forEach(::add) 36 | } 37 | 38 | @UnsafeAccessors 39 | internal inline fun reset(row: Int, archetype: Archetype) { 40 | this.row = row 41 | this.archetype = archetype 42 | cachingAccessors.forEach { it.updateCache(archetype) } 43 | } 44 | 45 | @PublishedApi 46 | @UnsafeAccessors 47 | @JvmField 48 | internal var row = 0 49 | 50 | @UnsafeAccessors 51 | val unsafeEntity: EntityId 52 | get() = this.archetype.getEntity(row) 53 | } 54 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/observers/EntityGetAsFlowTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers 2 | 3 | import com.mineinabyss.geary.helpers.entity 4 | import com.mineinabyss.geary.test.GearyTest 5 | import io.kotest.matchers.collections.shouldContainExactly 6 | import kotlinx.coroutines.launch 7 | import kotlinx.coroutines.test.UnconfinedTestDispatcher 8 | import kotlinx.coroutines.test.runTest 9 | import kotlin.test.Test 10 | 11 | class EntityGetAsFlowTest : GearyTest() { 12 | @Test 13 | fun `getAsFlow should correctly listen to entity updates`() = runTest { 14 | val entity = entity() 15 | val collected = mutableListOf() 16 | 17 | backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { 18 | entity.getAsFlow().collect { 19 | if(it != null) collected.add(it) 20 | else collected.add(0) 21 | } 22 | } 23 | entity.set(1) 24 | entity.set("other component") 25 | entity.set(2) 26 | entity.remove() 27 | entity.set(3) 28 | 29 | collected shouldContainExactly listOf(0, 1, 2, 0, 3) 30 | } 31 | 32 | @Test 33 | fun `getAsFlow should unregister itself when cancelled`() = runTest { 34 | val entity = entity() 35 | val collected = mutableListOf() 36 | 37 | backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { 38 | entity.getAsFlow().collect { 39 | if(it != null) collected.add(it) 40 | else collected.add(0) 41 | } 42 | val collecting = launch(UnconfinedTestDispatcher(testScheduler)) { 43 | entity.getAsFlow().collect { 44 | if(it != null) collected.add(it) 45 | else collected.add(0) 46 | } 47 | } 48 | entity.set(1) 49 | collecting.cancel() 50 | entity.set(2) 51 | collected shouldContainExactly listOf(0, 1) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/datatypes/Family2ObjectArrayMapTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | import com.mineinabyss.geary.datatypes.family.family 4 | import com.mineinabyss.geary.datatypes.maps.Family2ObjectArrayMap 5 | import com.mineinabyss.geary.engine.ComponentProvider 6 | import io.kotest.matchers.collections.shouldBeEmpty 7 | import io.kotest.matchers.collections.shouldContainExactly 8 | import io.kotest.matchers.shouldBe 9 | import org.junit.jupiter.api.Test 10 | import kotlin.reflect.KClassifier 11 | 12 | class Family2ObjectArrayMapTest { 13 | @Test 14 | fun addAndRemoveOne() { 15 | val familyMap = Family2ObjectArrayMap() 16 | familyMap.add("a", EntityType(ulongArrayOf(1uL, 2uL, 3uL))) 17 | familyMap.match(family { has(1uL) }) shouldContainExactly listOf("a") 18 | familyMap.remove("a") 19 | familyMap.match(family { has(1uL) }).shouldBeEmpty() 20 | } 21 | 22 | 23 | @Test 24 | fun addAndRemoveReplacingWithOther() { 25 | val familyMap = Family2ObjectArrayMap() 26 | familyMap.add("a", EntityType(ulongArrayOf(1uL, 2uL, 3uL))) 27 | familyMap.add("b", EntityType(ulongArrayOf(1uL, 3uL))) 28 | familyMap.match(family { has(1uL) }).shouldContainExactly("a", "b") 29 | familyMap.remove("a") 30 | familyMap.match(family { 31 | has(1uL) 32 | }).shouldContainExactly("b") 33 | familyMap.match(family { 34 | has(1uL) 35 | has(2uL) 36 | }).shouldContainExactly() 37 | } 38 | 39 | @Test 40 | fun removeLastInArray() { 41 | val familyMap = Family2ObjectArrayMap() 42 | familyMap.add("a", EntityType(ulongArrayOf(1uL, 2uL, 3uL))) 43 | familyMap.add("b", EntityType(ulongArrayOf(1uL, 3uL))) 44 | familyMap.match(family { has(1uL) }).shouldContainExactly("a", "b") 45 | familyMap.remove("a") 46 | familyMap.remove("b") 47 | familyMap.match(family { 48 | has(1uL) 49 | }).shouldBeEmpty() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/EntityIdArray.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes 2 | 3 | import androidx.collection.MutableLongList 4 | import com.mineinabyss.geary.modules.Geary 5 | 6 | typealias EntityIdArray = ULongArray 7 | 8 | fun EntityIdArray.toEntityArray(world: Geary): EntityArray { 9 | return EntityArray(world, this) 10 | } 11 | 12 | fun MutableLongList.toEntityArray(world: Geary): EntityArray { 13 | return EntityArray(world, ULongArray(size) { get(it).toULong() }) 14 | } 15 | 16 | /** 17 | * An array of [EntityId]s with an associated [world]. 18 | * Avoids string boxed [GearyEntity] instances for each entity, with helpers to completely avoid boxing like [forEachId]. 19 | */ 20 | class EntityArray( 21 | val world: Geary, 22 | val ids: EntityIdArray, 23 | ) : Collection { 24 | override val size: Int get() = ids.size 25 | 26 | override fun isEmpty(): Boolean = ids.isEmpty() 27 | 28 | override fun iterator(): Iterator = object : Iterator { 29 | private var index = 0 30 | override fun hasNext(): Boolean = index < ids.size 31 | override fun next(): Entity = GearyEntity(ids[index++], world) 32 | } 33 | 34 | override fun containsAll(elements: Collection): Boolean { 35 | return elements.all { contains(it) } 36 | } 37 | 38 | override fun contains(element: Entity): Boolean { 39 | return ids.contains(element.id) 40 | } 41 | 42 | inline fun fastForEach(action: (Entity) -> Unit) { 43 | for (i in ids.indices) action(GearyEntity(ids[i], world)) 44 | } 45 | 46 | inline fun forEachId(action: (EntityId) -> Unit) { 47 | for (i in ids.indices) action(ids[i]) 48 | } 49 | 50 | inline fun flatMap(transform: (Entity) -> EntityArray): EntityArray { 51 | return ids.flatMapTo(arrayListOf()) { transform(Entity(it, world)).ids }.toULongArray().toEntityArray(world) 52 | } 53 | 54 | operator fun minus(other: Collection): EntityArray { 55 | return ids.minus(other.map { it.id }.toSet()).toULongArray().toEntityArray(world) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /addons/geary-prefabs/src/com/mineinabyss/geary/prefabs/configuration/components/CopyToInstances.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.prefabs.configuration.components 2 | 3 | import com.mineinabyss.geary.datatypes.Component 4 | import com.mineinabyss.geary.datatypes.Entity 5 | import com.mineinabyss.geary.serialization.SerializableComponents 6 | import com.mineinabyss.geary.serialization.serializers.SerializedComponents 7 | import com.mineinabyss.geary.serialization.setAllPersisting 8 | import kotlinx.serialization.Polymorphic 9 | import kotlinx.serialization.SerialName 10 | import kotlinx.serialization.Serializable 11 | 12 | /** 13 | * A component prefabs may use to specify a list of components which should be copied to instances of itself. 14 | * 15 | * These components are not active on the prefab itself and will be instantiated for every new instance of this class. 16 | * Components may be defined as simply [temporary] or [persisting], where persisting components will be added as such 17 | * in the Engine. 18 | */ 19 | @Serializable 20 | @SerialName("geary:copy_to_instances") 21 | data class CopyToInstances( 22 | private val temporary: SerializedComponents? = null, 23 | private val persisting: SerializedComponents? = null, 24 | ) { 25 | @Serializable 26 | private data class DeepCopy( 27 | val temporary: List<@Polymorphic Component>?, 28 | val persisting: List<@Polymorphic Component>?, 29 | ) 30 | 31 | fun decodeComponentsTo(entity: Entity) { 32 | val binaryFormat = entity.world.getAddon(SerializableComponents).formats.binaryFormat 33 | 34 | // This is the safest and cleanest way to deep-copy, even if a little performance intense. 35 | val encoded = binaryFormat.encodeToByteArray(DeepCopy.serializer(), DeepCopy(temporary, persisting)) 36 | val (instance, persist) = binaryFormat.decodeFromByteArray(DeepCopy.serializer(), encoded) 37 | 38 | if (instance != null) { 39 | entity.setAll(instance, override = false) 40 | } 41 | if (persist != null) { 42 | entity.setAllPersisting(persist, override = false) 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.datatypes.maps 2 | 3 | import androidx.collection.mutableObjectListOf 4 | import com.mineinabyss.geary.datatypes.BucketedULongArray 5 | import com.mineinabyss.geary.datatypes.EntityId 6 | import com.mineinabyss.geary.datatypes.EntityType 7 | import com.mineinabyss.geary.engine.archetypes.Archetype 8 | 9 | open class ArrayTypeMap : TypeMap { 10 | @PublishedApi 11 | internal val archList = mutableObjectListOf() 12 | 13 | @PublishedApi 14 | internal var archAndRow = BucketedULongArray() 15 | var size = 0 16 | 17 | // We don't return nullable record to avoid boxing. 18 | // Accessing an entity that doesn't exist is indicative of a problem elsewhere and should be made obvious. 19 | open fun getArchAndRow(entity: EntityId): ULong { 20 | return archAndRow[entity.toInt()] 21 | } 22 | 23 | override fun set(entity: EntityId, archetype: Archetype, row: Int) { 24 | val id = entity.toInt() 25 | archAndRow[id] = (indexOrAdd(archetype).toULong() shl 32) or row.toULong() 26 | } 27 | 28 | fun indexOrAdd(archetype: Archetype): Int { 29 | if (archetype.indexInRecords != -1) return archetype.indexInRecords 30 | val index = archList.indexOf(archetype) 31 | archetype.indexInRecords = index 32 | return if (index == -1) { 33 | archList.add(archetype) 34 | archList.lastIndex 35 | } else index 36 | } 37 | 38 | override fun remove(entity: EntityId) { 39 | val id = entity.toInt() 40 | archAndRow[id] = 0UL 41 | } 42 | 43 | override operator fun contains(entity: EntityId): Boolean { 44 | val id = entity.toInt() 45 | return id < archAndRow.size && archAndRow[id] != 0uL 46 | } 47 | 48 | 49 | inline fun runOn(entity: EntityId, run: (archetype: Archetype, row: Int) -> T): T { 50 | val info = getArchAndRow(entity) 51 | return run(archList[(info shr 32).toInt()], info.toInt()) 52 | } 53 | 54 | fun getType(entity: EntityId): EntityType = runOn(entity) { archetype, _ -> 55 | archetype.type 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /geary-core/src/com/mineinabyss/geary/helpers/Relationship.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.helpers 2 | 3 | import com.mineinabyss.geary.components.CouldHaveChildren 4 | import com.mineinabyss.geary.components.relations.ChildOf 5 | import com.mineinabyss.geary.datatypes.Entity 6 | 7 | /** Adds a [parent] entity to this entity. */ 8 | fun Entity.addParent(parent: Entity) { 9 | parent.add() // TODO temporarily in place until child queries are faster 10 | addRelation(parent) 11 | } 12 | 13 | /** Adds a list of [parents] entities to this entity. */ 14 | fun Entity.addParents(parents: Array) { 15 | parents.fastForEach { addParent(it) } 16 | } 17 | 18 | /** Removes a [parent], also unlinking this child from that parent. */ 19 | fun Entity.removeParent(parent: Entity) { 20 | removeRelation(parent) 21 | } 22 | 23 | /** Removes all of this entity's parents, also unlinking this child from them. */ 24 | fun Entity.clearParents() { 25 | parents.forEach { remove(it.id) } 26 | } 27 | 28 | /** Adds a [child] entity to this entity. */ 29 | fun Entity.addChild(child: Entity) { 30 | child.addParent(this) 31 | } 32 | 33 | /** Adds a list of [children] entities to this entity. */ 34 | fun Entity.addChildren(children: Array) { 35 | children.fastForEach { addChild(it) } 36 | } 37 | 38 | /** Removes a [child], also unlinking this parent from that child. */ 39 | fun Entity.removeChild(child: Entity) { 40 | child.removeParent(this) 41 | } 42 | 43 | /** Removes all of this entity's children, also unlinking this parent from them. */ 44 | fun Entity.clearChildren() { 45 | children.forEach { remove(it.id) } 46 | } 47 | 48 | /** Gets the first parent of this entity */ 49 | val Entity.parent: Entity? 50 | get() = with(world) { getRelations().firstOrNull()?.target?.toGeary() } 51 | 52 | /** Runs code on the first parent of this entity. */ 53 | inline fun Entity.onParent( 54 | parent: Entity? = this.parent, 55 | run: Entity.() -> Unit, 56 | ) { 57 | parent ?: return 58 | run(parent) 59 | } 60 | 61 | val Entity.parents: Set 62 | get() = with(world) { getRelations().mapTo(mutableSetOf()) { it.target.toGeary() } } 63 | -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/components/ComponentAsEntityProviderTest.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.components 2 | 3 | import com.mineinabyss.geary.helpers.componentId 4 | import com.mineinabyss.geary.helpers.entity 5 | import com.mineinabyss.geary.test.GearyTest 6 | import io.kotest.matchers.shouldBe 7 | import org.junit.jupiter.api.Test 8 | import org.koin.core.scope.Scope 9 | import org.koin.core.scope.ScopeCallback 10 | import org.koin.dsl.koinApplication 11 | import org.koin.dsl.module 12 | import kotlin.random.Random 13 | 14 | class ComponentAsEntityProviderTest : GearyTest() { 15 | @Test 16 | fun `should correctly register reserved components`() { 17 | entity() 18 | componentId() shouldBe ReservedComponents.ANY 19 | } 20 | } 21 | 22 | class Something { 23 | 24 | class A(val test: String) { 25 | } 26 | 27 | class B(val test: String) 28 | 29 | @Test 30 | fun main() { 31 | val app = koinApplication() { 32 | modules(module { 33 | single { "default" } 34 | scope { 35 | scoped { 36 | registerCallback(object : ScopeCallback { 37 | override fun onScopeClose(scope: Scope) { 38 | println("Closed") 39 | } 40 | }) 41 | Random.nextInt() 42 | } 43 | scoped { 44 | get().toString() 45 | } 46 | } 47 | scope { 48 | scoped { get().toString() } 49 | scoped { 0.1 } 50 | // scoped { "B" + Random.nextInt(100) } 51 | } 52 | }) 53 | } 54 | app.koin.createScope("A").apply { 55 | get() 56 | }.close() 57 | val scopeB = app.koin.createScope("A").get().let { println(it) } 58 | // val scopeB = app.koin.createScope().apply { 59 | // linkTo(app.koin.getScope("A")) 60 | // } 61 | 62 | // scopeA.linkTo(scopeB) 63 | // scopeA.get().let { println(it) } 64 | // scopeB.get().let { println(it) } 65 | // scopeA.close() 66 | } 67 | } -------------------------------------------------------------------------------- /geary-core/test@jvm/com/mineinabyss/geary/observers/ObserverTypeTests.kt: -------------------------------------------------------------------------------- 1 | package com.mineinabyss.geary.observers 2 | 3 | import com.mineinabyss.geary.helpers.componentId 4 | import com.mineinabyss.geary.helpers.entity 5 | import com.mineinabyss.geary.test.GearyTest 6 | import com.mineinabyss.geary.modules.observe 7 | import com.mineinabyss.geary.modules.observeWithData 8 | import com.mineinabyss.geary.systems.query.query 9 | import io.kotest.matchers.shouldBe 10 | import io.kotest.matchers.types.shouldBeInstanceOf 11 | import org.junit.jupiter.api.BeforeEach 12 | import kotlin.test.Test 13 | 14 | class ObserverTypeTests : GearyTest() { 15 | class MyEvent 16 | class OtherEvent 17 | 18 | @BeforeEach 19 | fun reset() = resetEngine() 20 | 21 | @Test 22 | fun `should observe event regardless of holding data when not observing event data`() { 23 | var called = 0 24 | observe().exec { called++ } 25 | val entity = entity() 26 | 27 | called shouldBe 0 28 | entity.emit() 29 | called shouldBe 1 30 | entity.emit(MyEvent()) 31 | called shouldBe 2 32 | entity.emit() 33 | called shouldBe 2 34 | } 35 | 36 | @Test 37 | fun `should not observe event without data when observing data`() { 38 | var called = 0 39 | observeWithData().exec { 40 | event.shouldBeInstanceOf() 41 | called++ 42 | } 43 | val entity = entity() 44 | 45 | called shouldBe 0 46 | entity.emit() 47 | called shouldBe 0 48 | entity.emit(MyEvent()) 49 | called shouldBe 1 50 | entity.emit(OtherEvent()) 51 | called shouldBe 1 52 | } 53 | 54 | @Test 55 | fun `should observe events involving component when filtering one involved component`() { 56 | var called = 0 57 | observe().involving(query()).exec { (int) -> called += int } 58 | 59 | val entity = entity() 60 | 61 | called shouldBe 0 62 | entity.emit() 63 | called shouldBe 0 64 | entity.emit(involving = componentId()) 65 | called shouldBe 0 66 | entity.set(10) 67 | entity.emit(involving = componentId()) 68 | called shouldBe 10 69 | } 70 | } 71 | --------------------------------------------------------------------------------