Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: API to get a family of entities. #48

Closed
Kietyo opened this issue May 31, 2022 · 11 comments
Closed

Feature Request: API to get a family of entities. #48

Kietyo opened this issue May 31, 2022 · 11 comments
Labels
enhancement New feature or request

Comments

@Kietyo
Copy link

Kietyo commented May 31, 2022

I want a way to be able to easily get a family of entities with certain components that's decoupled from the systems.

Example:

        val familyContainer = world.familyContainer(
            allOf = ...,
            anyOf = ...,
            noneOf = ...,
        )
        val entities: List<Entity> = family.getEntities()

The family container should be automatically updated on addition/removal of entities and addition/removal of their components.

@Quillraven
Copy link
Owner

Quillraven commented May 31, 2022

Hi @Kietyo ,

I just released a version yesterday that contains this feature. Here is a snippet from the ReadMe. The important part is the world.family call and its sorting and iteration:

fun main() {
    val world = World {}
    val e1 = w.entity { 
        add<MoveComponent> { speed = 70f } 
    }
    val e2 = w.entity { 
        add<MoveComponent> { speed = 50f }
        add<DeadComponent>()
    }
    val e3 = w.entity { 
        add<MoveComponent> { speed = 30f } 
    }

    // get family for entities with a MoveComponent
    // and without a DeadComponent
    val family = world.family(
        allOf = arrayOf(MoveComponent::class),
        noneOf = arrayOf(DeadComponent::class),
    )

    // you can sort entities of a family
    val moves = world.mapper<MoveComponent>()
    family.sort(compareEntity { entity1, entity2 -> moves[entity1].speed.compareTo(moves[entity2].speed) })
    
    // And you can iterate over entities of a family.
    // In this example it will iterate in following order:
    // 1) e3
    // 2) e1
    family.forEach { entity ->
        // do something with the entity
    }
}

Does this cover your needs for this use case?

@Quillraven Quillraven added the enhancement New feature or request label May 31, 2022
@Kietyo
Copy link
Author

Kietyo commented Jun 1, 2022

Thanks! This does, are you supporting the korge-fleks version BTW? Cause that's the version I'm using:

https://github.com/korlibs/korge-next/tree/main/korge-fleks

@Quillraven
Copy link
Owner

I was afraid that you say that 😅
The KMP Version was done by @jobe-m. There were a few updates since then in the JVM version and I have of course plans to integrate that into the KMP Version as well but this will take time, sorry.

Any help is appreciated! I myself try to update that version by end of June.

I need to make a small release to the JVM version again this week for some Bugfixes and I try to support your Family listener idea. This version will then be the base for the next KMP Version as well.

Thank you for your input and suggestions! And sorry for the delay with the KMP part. This will be easier in the future once those two versions get merged into one 😉

@Kietyo
Copy link
Author

Kietyo commented Jun 1, 2022

I see, I've started contributing to the korge-fleks version actually... fyi

korlibs-archive/korge-next#715

It's easier for me to modify that version since I already have the korge-next repo integrated into my workflow.

@jobe-m
Copy link
Collaborator

jobe-m commented Jun 1, 2022

Thanks for notifing me here 👍
I will update the korge-fleks copy with @Quillraven's latest changes to get it in sync again.
After that I plan to discuss if and how we upsteam the changes from korlibs-archive/korge-next#715 to the KMP and main branch here.

@Quillraven
Copy link
Owner

I think in the next branch I made a proper KMP gradle setup and I was able to add it to KorGE as a normal dependency as mentioned in the ReadMe. I suggest that this is the way to go but up for discussion :)

@Quillraven
Copy link
Owner

@Kietyo / @jobe-m : I tried something now for the FamilyListener support. The interface will look like this:

/**
 * Interface of a [family][Family] listener that gets notified when an
 * [entity][Entity] gets added to, or removed from a family.
 */
interface FamilyListener {
    /**
     * Function that gets called when an [entity][Entity] gets added to a [family][Family].
     */
    fun onEntityAdded(entity: Entity) = Unit

    /**
     * Function that gets called when an [entity][Entity] gets removed from a [family][Family].
     */
    fun onEntityRemoved(entity: Entity) = Unit
}

The family itself has three functions:

/**
 * Adds the given [listener] to the list of [FamilyListener].
 */
fun addFamilyListener(listener: FamilyListener) = listeners.add(listener)

/**
 * Removes the given [listener] from the list of [FamilyListener].
 */
fun removeFamilyListener(listener: FamilyListener) = listeners.removeValue(listener)

/**
 * Returns true if an only if the given [listener] is part of the list of [FamilyListener].
 */
operator fun contains(listener: FamilyListener) = listener in listeners

That gives users the possibility to do something like this:

fun main() {
    val familyListener = object : FamilyListener {
        override fun onEntityAdded(entity: Entity) {
            // do something when an entity gets added to the family
        }

        override fun onEntityRemoved(entity: Entity) {
            // do something when an entity gets removed of a family
        }
    }
    val world = World {}
    val family = world.family(
        allOf = arrayOf(MoveComponent::class),
        noneOf = arrayOf(DeadComponent::class),
    )
    family.addFamilyListener(familyListener)

    // this will notify the listener via its onEntityAdded function
    val entity = world.entity {
        add<MoveComponent>()
        add<DeadComponent>()
    }

    // this will notify the listener via its onEntityRemoved function
    world.remove(entity)
}

I think this is fine but there is a special case that I can think of, where this won't work. When a user is creating entities in a system's constructor then the FamilyListener will of course not be notified because it does not exist at that point in time.

My proposal would be to do something similar like we do it with ComponentListener meaning that you can add FamilyListener setup to the world's configuration. That way we can create such listeners and their families before systems get created and then the scenario described above will work again. It could look something like:

@AllOf([Cmp1::clas, Cmp2::class])
@NoneOf([Cmp3::class])
class MyFamilyListener : FamilyListener {
  override fun onEntityAdded(entity: Entity) {
  }

  override fun onEntityRemoved(entity: Entity) {
  }
}

val w = World {
  familyListener<MyFamilyListener>()
}

or for the KMP version:

class MyFamilyListener : FamilyListener(
  allOf = arrayOf(Cmp1::class, Cmp2::class),
  noneOf = arrayOf(Cmp3::class)
) {
  override fun onEntityAdded(entity: Entity) {
  }

  override fun onEntityRemoved(entity: Entity) {
  }
}

val w = World {
  familyListener(::MyFamilyListener)
}

What do you think about that?

@Kietyo
Copy link
Author

Kietyo commented Jun 2, 2022

I like the proposal, thanks!

@jobe-m
Copy link
Collaborator

jobe-m commented Jun 2, 2022

The proposal looks good!
BTW yesterday I merged the changes from master branch into the next branch. After running the unit tests locally I will create a PR -> #50

@Quillraven
Copy link
Owner

Quillraven commented Jun 6, 2022

@Kietyo : Please have a look at #53 and try out the next branch if that suits your needs.

@Quillraven
Copy link
Owner

I will close this issue as well. It is part of #53. If you have feedback then please let me know in the PR.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants