Skip to content

Commit

Permalink
Merge pull request #95 from MineInAbyss/develop
Browse files Browse the repository at this point in the history
Archetype cleanup, Prefab addon improvements
  • Loading branch information
0ffz authored Dec 25, 2023
2 parents 04799b2 + a32435e commit 5b6f86d
Show file tree
Hide file tree
Showing 18 changed files with 429 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ package com.mineinabyss.geary.prefabs
import co.touchlab.kermit.Severity
import com.benasher44.uuid.Uuid
import com.mineinabyss.geary.components.relations.NoInherit
import com.mineinabyss.geary.datatypes.Component
import com.mineinabyss.geary.datatypes.Entity
import com.mineinabyss.geary.helpers.entity
import com.mineinabyss.geary.helpers.with
import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.prefabs.configuration.components.Prefab
import com.mineinabyss.geary.prefabs.helpers.inheritPrefabs
import com.mineinabyss.geary.prefabs.serializers.ComponentListAsMapSerializer
import com.mineinabyss.geary.serialization.dsl.serializableComponents
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.builtins.ListSerializer
import okio.Path

class PrefabLoader {
Expand Down Expand Up @@ -44,28 +41,35 @@ class PrefabLoader {

/** If this entity has a [Prefab] component, clears it and loads components from its file. */
fun reread(entity: Entity) {
entity.with { prefab: Prefab, key: PrefabKey ->
entity.clear()
loadFromPath(key.namespace, prefab.file ?: return, entity)
entity.inheritPrefabs()
}
val prefab = entity.get<Prefab>() ?: error("Entity was not an already loaded prefab")
val key = entity.get<PrefabKey>() ?: error("Entity did not have a prefab key")
val file = prefab.file ?: error("Prefab did not have a file")
entity.clear()

// set basics here as well in case load fails
entity.addRelation<NoInherit, Prefab>()
entity.addRelation<NoInherit, Uuid>()
entity.set(key)
entity.set(prefab)
loadFromPath(key.namespace, file, entity).getOrThrow()
entity.inheritPrefabs()
}

/** Registers an entity with components defined in a [path], adding a [Prefab] component. */
fun loadFromPath(namespace: String, path: Path, writeTo: Entity? = null): Result<Entity> {
return runCatching {
val serializer = ListSerializer(PolymorphicSerializer(Component::class))
val serializer = ComponentListAsMapSerializer()
val ext = path.name.substringAfterLast('.')

val decoded = formats[ext]?.decodeFromFile(serializer, path)
?: throw IllegalArgumentException("Unknown file format $ext")

val key = PrefabKey.of(namespace, path.name.substringBeforeLast('.'))

val entity = writeTo ?: entity()
entity.set(Prefab(path))
entity.addRelation<NoInherit, Prefab>()
entity.addRelation<NoInherit, Uuid>()
entity.set(Prefab(path))
entity.setAll(decoded)

val key = PrefabKey.of(namespace, path.name.substringBeforeLast('.'))
entity.set(key)
entity
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.mineinabyss.geary.prefabs.serializers

import com.mineinabyss.geary.datatypes.GearyComponent
import com.mineinabyss.geary.serialization.dsl.serializableComponents
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.PolymorphicKind
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildSerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder


class GearyComponentSerializer : KSerializer<GearyComponent> {
@OptIn(InternalSerializationApi::class)
override val descriptor: SerialDescriptor =
buildSerialDescriptor("GearyComponentSerializer", PolymorphicKind.SEALED)

override fun deserialize(decoder: Decoder): GearyComponent {
TODO("Not yet implemented")
}

override fun serialize(encoder: Encoder, value: GearyComponent) {
TODO("Not yet implemented")
}

}

class ComponentListAsMapSerializer : KSerializer<List<GearyComponent>> {
val keySerializer = String.serializer()
val valueSerializer = GearyComponentSerializer()

override val descriptor: SerialDescriptor
get() = MapSerializer(keySerializer, valueSerializer).descriptor

override fun deserialize(decoder: Decoder): List<GearyComponent> {
val namespaces = mutableListOf<String>()
val components = mutableListOf<GearyComponent>()
val compositeDecoder = decoder.beginStructure(descriptor)
while (true) {
val index = compositeDecoder.decodeElementIndex(descriptor)
if (index == CompositeDecoder.DECODE_DONE) break

val startIndex = components.size * 2
val key: String = compositeDecoder.decodeSerializableElement(descriptor, startIndex + index, keySerializer)
when (key) {
"namespaces" -> {
val valueSerializer = ListSerializer(String.serializer())
val newIndex =
compositeDecoder.decodeElementIndex(MapSerializer(keySerializer, valueSerializer).descriptor)
val namespacesList = compositeDecoder.decodeSerializableElement(descriptor, newIndex, valueSerializer)
namespaces.addAll(namespacesList)
}
else -> {
val foundValueSerializer =
serializableComponents.serializers.getSerializerFor(key, GearyComponent::class, namespaces) as? KSerializer<Any>
?: error("No component serializer registered for $key")
val newDescriptor = MapSerializer(keySerializer, foundValueSerializer).descriptor
val newIndex = compositeDecoder.decodeElementIndex(newDescriptor)
val decodedValue = compositeDecoder.decodeSerializableElement<Any>(
descriptor = newDescriptor,
index = newIndex,
deserializer = foundValueSerializer,
)
components += decodedValue
}
}
}
compositeDecoder.endStructure(descriptor)
return components.toList()
}

override fun serialize(encoder: Encoder, value: List<GearyComponent>) {
TODO("Not implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ interface ComponentSerializers {

fun <T : Component> getSerializerFor(
key: String,
baseClass: KClass<in T>
): DeserializationStrategy<out T>?
baseClass: KClass<in T>,
namespaces: List<String> = emptyList()
): DeserializationStrategy<T>?

fun <T : Component> getSerializerFor(kClass: KClass<in T>): DeserializationStrategy<out T>?
fun getSerialNameFor(kClass: KClass<out Component>): String?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@ class SerializersByMap(

override fun <T : Component> getSerializerFor(
key: String,
baseClass: KClass<in T>
): DeserializationStrategy<out T>? =
module.getPolymorphic(baseClass = baseClass, serializedClassName = key)
baseClass: KClass<in T>,
namespaces: List<String>,
): DeserializationStrategy<T>? =
if (key.hasNamespace())
module.getPolymorphic(baseClass = baseClass, serializedClassName = key)
else {
namespaces.firstNotNullOfOrNull { namespace ->
module.getPolymorphic(baseClass = baseClass, serializedClassName = "$namespace:$key")
} ?: error("No serializer found for $key in any of the namespaces $namespaces")
}

override fun <T : Component> getSerializerFor(kClass: KClass<in T>): DeserializationStrategy<out T>? {
val serialName = getSerialNameFor(kClass) ?: return null
Expand All @@ -32,4 +39,10 @@ class SerializersByMap(

override fun getSerialNameFor(kClass: KClass<out Component>): String? =
component2serialName[kClass]

private fun String.hasNamespace(): Boolean = contains(":")
private fun String.prefixNamespaceIfNotPrefixed(): String =
if (!hasNamespace())
"geary:${this}"
else this
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.mineinabyss.geary.components

/**
* An entity's archetype with this component may stay even if empty.
* Useful for entities that are created and removed often, ex events.
*/
sealed class KeepArchetype
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.mineinabyss.geary.datatypes

import com.mineinabyss.geary.annotations.optin.DangerousComponentOperation
import com.mineinabyss.geary.components.RequestCheck
import com.mineinabyss.geary.components.events.AddedComponent
import com.mineinabyss.geary.components.events.FailedCheck
import com.mineinabyss.geary.components.relations.InstanceOf
import com.mineinabyss.geary.components.relations.Persists
import com.mineinabyss.geary.datatypes.family.family
Expand Down Expand Up @@ -322,6 +324,23 @@ value class Entity(val id: EntityId) {
result(event)
}

fun callCheck(
source: Entity? = null,
): Boolean {
return callEvent({
add<RequestCheck>()
}, source) { !it.has<FailedCheck>()}
}
inline fun callCheck(
crossinline init: Entity.() -> Unit,
source: Entity? = null,
): Boolean {
return callEvent({
init()
add<RequestCheck>()
}, source) { it.has<FailedCheck>()}
}

/** Calls an event using a specific [entity][event] on this entity. */
fun callEvent(event: Entity, source: Entity? = null) {
eventRunner.callEvent(this, event, source)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,17 @@ expect class CompId2ArchetypeMap() {
operator fun get(id: GearyComponentId): Archetype?
operator fun set(id: GearyComponentId, archetype: Archetype)

fun entries(): Set<Map.Entry<ULong, Archetype>>

fun clear()

fun remove(id: GearyComponentId)

operator fun contains(id: GearyComponentId): Boolean

fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype

val size: Int
}

class CompId2ArchetypeMapViaMutableMap {
Expand All @@ -22,6 +30,18 @@ class CompId2ArchetypeMapViaMutableMap {
inner[id] = archetype
}

fun entries(): Set<Map.Entry<ULong, Archetype>> = inner.entries

fun remove(id: GearyComponentId) {
inner.remove(id)
}

fun clear() {
inner.clear()
}

val size: Int get() = inner.size

operator fun contains(id: GearyComponentId): Boolean = inner.containsKey(id)

fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ import com.mineinabyss.geary.modules.geary
/**
* A map of [ComponentId]s to Arrays of objects with the ability to make fast queries based on component IDs.
*/
internal class Family2ObjectArrayMap<T> {
private val elements = mutableListOf<T>()
internal class Family2ObjectArrayMap<T>(
val getIndex: ((T) -> Int)? = null,
val setIndex: ((T, Int) -> Unit)? = null
) {
private val _elements = mutableListOf<T>()
private val elementTypes = mutableListOf<EntityType>()

val elements: List<T> get() = _elements

/**
* A map of component ids to a [BitSet] where each set bit means that the element at its index in [elements]
* A map of component ids to a [BitSet] where each set bit means that the element at its index in [_elements]
* contains this component in its type.
*
* ### Extra rules are applied for relations:
Expand All @@ -24,9 +29,17 @@ internal class Family2ObjectArrayMap<T> {
private val componentMap = mutableMapOf<Long, BitSet>()

fun add(element: T, type: EntityType) {
elements += element
elementTypes += type
val index = elements.lastIndex
set(element, type, _elements.size)
}

internal fun set(element: T, type: EntityType, index: Int) {
if (index == _elements.size) {
_elements.add(element)
elementTypes.add(type)
} else {
_elements[index] = element
elementTypes[index] = type
}
type.forEach { id ->
fun set(i: ComponentId) = componentMap.getOrPut(i.toLong()) { bitsOf() }.set(index)

Expand All @@ -38,6 +51,47 @@ internal class Family2ObjectArrayMap<T> {
}
set(id)
}
setIndex?.invoke(element, index)
}

private fun clearBits(type: EntityType, index: Int) {
type.forEach { id ->
fun clear(i: ComponentId) = componentMap[i.toLong()]?.clear(index)

// See componentMap definition for relations
if (id.isRelation()) {
val relation = Relation.of(id)
clear(Relation.of(relation.kind, geary.components.any).id)
clear(Relation.of(geary.components.any, relation.target).id)
}
clear(id)
}
}

internal fun remove(element: T) {
val index = getIndex?.invoke(element) ?: _elements.indexOf(element)
// Clear data for current element
val type = elementTypes[index]

clearBits(type, index)

if(index == _elements.lastIndex) {
_elements.removeAt(index)
elementTypes.removeAt(index)
return
}

val lastElement = _elements.last()
val lastType = elementTypes.last()

clearBits(lastType, _elements.lastIndex)

// copy data from last element
set(lastElement, lastType, index)

// remove last element
_elements.removeAt(_elements.lastIndex)
elementTypes.removeAt(elementTypes.lastIndex)
}

/**
Expand All @@ -57,10 +111,11 @@ internal class Family2ObjectArrayMap<T> {
is Family.Selector.AndNot -> {
// We take current bits and removed any matched inside, if null is returned, all bits are removed
val inside = family.andNot.reduceToBits(BitSet::or) ?: return bitsOf()
(bits ?: bitsOf().apply { set(0, elements.lastIndex) }).apply {
(bits ?: bitsOf().apply { set(0, _elements.lastIndex) }).apply {
andNot(inside)
}
}

is Family.Selector.Or -> family.or.reduceToBits(BitSet::or)
is Family.Leaf.Component -> componentMap[family.component.toLong()]?.copy() ?: bitsOf()
is Family.Leaf.AnyToTarget -> {
Expand All @@ -74,6 +129,7 @@ internal class Family2ObjectArrayMap<T> {
}
} ?: bitsOf()
}

is Family.Leaf.KindToAny -> {
// The bits for relationId in componentMap represent archetypes with any relations containing kind
val relationId = Relation.of(family.kind, geary.components.any).id
Expand All @@ -89,9 +145,9 @@ internal class Family2ObjectArrayMap<T> {
}

fun match(family: Family): List<T> {
val bits = getMatchingBits(family, null) ?: return elements.toList()
val bits = getMatchingBits(family, null) ?: return _elements.toList()
val matchingElements = ArrayList<T>(bits.cardinality)
bits.forEachBit { matchingElements += elements[it] }
bits.forEachBit { matchingElements += _elements[it] }
return matchingElements
}
}
Loading

0 comments on commit 5b6f86d

Please sign in to comment.