Welcome to Fleks Discussions! #1
Replies: 3 comments 12 replies
-
@czyzby / @Jkly / @nanodeath: I saw that all of you were part of the initial I am currently super busy at work and plan to have my holidays "soonish". That's why I might not be able to implement changes that fast. Thank you for your help and feedback in advance! Maybe we can build a great ECS in Kotlin together and provide an alternative to Ashley/Artemis ;) |
Beta Was this translation helpful? Give feedback.
-
Use a @JvmInline value class Entity(val id: Int)
interface Component
data class Position(val x: Float, val y: Float): Component
class System {
inline fun <reified T: Component> Entity.getComponent(): T =
TODO("Use $id to extract ${T::class} component.")
}
fun example() {
val system = System()
val entity = Entity(1)
system.run {
val position = entity.getComponent<Position>()
}
}
It kinda does with // With generics:
interface Component {
companion object {
val COMPONENT_ID = AtomicInteger()
}
}
abstract class Mapper<T: Component>(val id: Int = Component.COMPONENT_ID.getAndIncrement())
data class Position(val x: Float = 0f, val y: Float = 0f): Component {
companion object ID: Mapper<Position>()
}
class System {
fun <T: Component> getComponent(mapper: Mapper<T>): T = TODO("Fully typed, based on mapper!")
}
fun example() {
val system = System()
// No casting or generic required, will infer Position type.
val position = system.getComponent(Position.ID)
}
Proof of concept that would be more flexible in terms of custom IDs/multiple systems, but requires more user setup: interface Component
typealias MapperIdProvider = () -> Int
class ThreadSafeMapperIdProvider: MapperIdProvider {
private val id = AtomicInteger()
override fun invoke(): Int = id.getAndIncrement()
}
abstract class Mapper<T: Component>(idProvider: MapperIdProvider) {
val id: Int = idProvider()
}
// Usage (library user POV):
val idProvider = ThreadSafeMapperIdProvider() // Might want a shorter name for that, just sayin'.
data class Position(val x: Float, val y: Float) : Component {
companion object ID : Mapper<Position>(idProvider)
// Can work for multiple worlds - in fact, object does not have to be a companion in the first place:
object MY_SPECIAL_SYSTEM_ID : Mapper<Position>(TODO("Some other provider"))
}
fun example() {
// Both work:
Position.ID
Position.MY_SPECIAL_SYSTEM_ID
}
I'd go for separate classes, actually. Maybe not necessarily for |
Beta Was this translation helpful? Give feedback.
-
I will post here some things that I noticed during the development of the RPG tutorial game. Until now I could solve things differently but I think they might make sense to add it to Fleks:
|
Beta Was this translation helpful? Give feedback.
-
👋 Welcome!
Discussions is the place to talk about Fleks like new ideas or feedback for the library. I hope that you:
build together 💪.
Some technical details that I found out during the development of Fleks
Entity as Class vs Entity as Int
Array<Entity>
is slower than having anIntArray
. Also, creating an instance ofEntity
is slower than creating anInt
Int
internally for storing entitiesEntity
class but I think we don't need that for Fleks. With a clean API in Kotlin, having anInt
as entity shouldn't be an issueComponentMapper
<KClass<out Component>,Component>
. However, this is very slow because:ComponentMapper
. To keep it simple: it assigns a unique index to a component and this index is used for an array that stores all component instances of that typeInt
which is different for each component. Since that is not possible, we need thoseComponentMapper
instances@Component
which automatically assigns a static int to that class with a unique number. But I didn't understannd how that could be used because Fleks internally needs to access that index in the code and since it doesn't know about that static int it cannot use it. Maybe I missed something here but for now we have to live with the mappersSystem
tickRate
which can be used to create the different combinations of systems that Ashley/Artemis have.tickRate
is 0 or not and then handles it slightly differently but in my opinion this can be ignored for a cleaner/more user-friendly APICustom Collections
Bag
: it is basically a flexibleArray
that resizes automatically and doesn't keep the order of added entries. Since arrays are the fastest way to store/iterate over things, this is a very useful collectionBitArray
: Kotlin implementation of aBitSet
. It is based on LibGDX'sBits
implementation and not finalized yet. I think we also need that for multi platform because Java'sBitSet
is not available in all backends?IntBag
implementation to avoid autoboxingFamily
BitSet
than adding anInt
to aSet<Int>
. I also tried a custom implementation ofIntSet
to avoid autoboxing but it didn't even get close to the speed of aBitSet
BitSet
is used to remember updated entities which are part of the familyWorld.configureEntity
for that to only make one notification call but I am not happy with this part of the API yet. Would be better if users can directly use theComponentMapper
to add/remove components but I don't know yet how to make then only one notification call to interested familiesBitSet
is slower than iterating over anIntArray
because there can be empty space between two set bits. That's the reason why we also have anIntBag
per family to store the active entity ids for the fastest way to iterate over themPooling vs Non-pooling for Components
reset
method for components which is very often just a redundant assignment of values that you already do in the constructorBeta Was this translation helpful? Give feedback.
All reactions