-
Notifications
You must be signed in to change notification settings - Fork 20
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
#123: aggregated family hooks that trigger IntervalSystems #128
#123: aggregated family hooks that trigger IntervalSystems #128
Conversation
e6539c9
to
67f0dfd
Compare
67f0dfd
to
aa78623
Compare
Thank you for the contribution! It looks already pretty good. I have some feedback though:
I fiddled around yesterday a little bit with Kotlin features to combine lambdas. Your version is fine but in case you are interested, it might shorten the hook setup code a little bit. Output is "global class1 class2": typealias FamilyHook = () -> Unit
val globalHook: FamilyHook = {
println("global")
}
abstract class System {
open val familyHook: FamilyHook? = null
}
class System1 : System() {
override val familyHook = {
println("class1")
}
}
class System2 : System() {
override val familyHook = {
println("class2")
}
}
class System3 : System()
fun main() {
val systems = listOf(System1(), System2(), System3())
val systemHooks: List<FamilyHook> = systems.mapNotNull { it.familyHook }
// if there is no globalHook it should just be { systemHooks.forEach { it() } }
val resultHook: () -> Unit = { globalHook().also { systemHooks.forEach { it() } } }
resultHook()
} I also read several articles yesterday about how to find out if a method is overridden by a subclass in Kotlin. From what I know there is no multi-platform way to do it and the only one that I found was using kotlin-reflect library which I don't want to introduce and I think it also only worked for JVM. Therefore, unfortunately I don't see a way to achieve it with methods and that's why I'd vote for FamilyHook properties like we are already doing it in the WorldConfiguration or with EntityComparators. What I don't like here (besidese that methods are more intuitive in the context of a class) is that we need two separate properties for each system class for add and remove hook. This brings me to a new idea. It is similar to what we briefly discussed in the issue. Something like: configureWorld {
systems {
add(MySystem(), MySystem::onAddEntity, MySystem::onRemoveEntity)
}
} The idea is that users can optionally create methods in their systems that take an entity and return nothing (=FamilyHook). They can then register it in the world configuration and we can then create the final family hook like you are doing it right now. The advantage of this approach is:
The downside is some extra configuration in the Because to be honest what we are doing here can already be done with the already existing FamilyHook functionality anyway. It is just some nice "syntax sugar" to set it up in a more intuitive way. Anyway, I am still not sure what is the best way. What do you think about the new idea? |
It actually crossed my mind at the very last moment and I decided to include it here. But it sounds like it is its own big thing, it touches some areas outside the
Boy, it goes deep, haha. I like your solution of specifying the hooks through system configuration. It's the most explicit and flexible, but also leads to some potentially long declarations like
I have another idea from the old OOP world, we can introduce two interfaces interface OnEntityAddFamilyHook {
fun onEntityAdd(entity: Entity)
}
interface OnEntityAddFamilyHook {
fun onEntityRemove(entity: Entity)
}
class MySystem : IteratingSystem(family { }),
OnEntityAddFamilyHook,
OnEntityRemoveFamilyHook {
override fun OnEntityAdd(entity: Entity) { }
override fun onEntityRemove(entity: Entity) { }
} That should keep it simple enough while still being explicit. The only confusion could be if someone tries to add those interfaces to
This looks good, thanks! I will adjust the hook aggregation code to your recommendations. The only thing that always bothers me is the way Kotlin compiler treats |
I like the idea of the interfaces although it is a new concept in Fleks that was never used but I think in this scenario it is totally fine. For the problem with the |
…nterfaces. Use more idomatic Kotlin syntax for agregated family hook setup.
@Quillraven the PR is ready, please review it.
|
db3525e
to
031010b
Compare
Merge family hook system tests to the main system test file.
031010b
to
61986c1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me besides some minor details.
About the test: please extract it to a separate file like FamilySystemHookTest
or something similar. The reason is that - to be honest - the test files are pretty messy in my opinion and sometimes quite big with a lot of class definitions at the top. This happened in the first versions of Fleks where I just wanted to quickly write down the tests for all the cases I had in mind and I didn't really care a lot about the beauty of the test cases ;)
For new features like this, in order to not make the already messy test files even messier, please create a new file.
Also, can you please add a test to what happens, when an entity gets created in the constructor of a system and that would end up in the family? I am pretty sure that we already have a similar test for this but I am not 100% sure. Such entities should still call the onAdd
family hook.
And besides that I want to thank you for your time and contribution! It is a cool convenience feature and good addition to Fleks! When I release a new stable version, I will also document this new feature in the wiki.
edit: and also please merge/rebase to master once more because there is a conflict in SystemTest.kt
.
src/commonTest/kotlin/com/github/quillraven/fleks/SystemTest.kt
Outdated
Show resolved
Hide resolved
src/commonTest/kotlin/com/github/quillraven/fleks/SystemTest.kt
Outdated
Show resolved
Hide resolved
src/commonTest/kotlin/com/github/quillraven/fleks/SystemTest.kt
Outdated
Show resolved
Hide resolved
src/commonTest/kotlin/com/github/quillraven/fleks/SystemTest.kt
Outdated
Show resolved
Hide resolved
Move and clean system family hook tests.
@Quillraven thanks for the feedback. I made the necessary changes. Please note, that currently global family hooks and system family hooks work inconsistently unless the world is fully configured (aggregated hooks get created at the very end of This links back to my |
Tests look a lot cleaner now, thank you! What do you mean with the inconsistency? I know that I took extra care for families to behave correctly when entities get created in a system's constructor, if I remember correctly. Should be this code piece: internal fun family(definition: FamilyDefinition): Family {
if (definition.isEmpty()) {
throw FleksFamilyException(definition)
}
val (defAll, defNone, defAny) = definition
var family = allFamilies.find { it.allOf == defAll && it.noneOf == defNone && it.anyOf == defAny }
if (family == null) {
family = Family(defAll, defNone, defAny, this)
allFamilies += family
// initialize a newly created family by notifying it for any already existing entity
+ entityService.forEach { family.onEntityCfgChanged(it, entityService.compMasks[it.id]) }
}
return family
} What happens in following scenario?
I assume this works with the exisitng global hook configuration because that is configured before systems are created and the hook logic itself is final and therefore it just works. With these new changes now I assume SystemB is not notified because it was not initialized/created when the entity in SystemA is created. If that is true we need to come up with a solution for it. A quick idea is that in your I am of course not sure if it behaves like that because I didn't have time to try it out but I think it does. Anyway, I am sure there is a solution for it and it is hopefully not super complicated to solve. I guess it can be solved with a "one-time special care" logic during configuration phase. Also, adding an |
I think the entity creation in a system's constructor is a rather exceptional use-case. I think I never did that but I still wanted to support it because someone might need it. However, I am not sure if the problem above is easily solveable, so an alternative might be:
|
I agree with your reasoning that supporting entity creation in system constructors is important, but just for the sake of backward compatibility. However, moving forward with the configuration features, it would be crucial to impose this one rule that the world cannot be modified (entities added) during configuration time. It would help to keep things simple in the configuration logic, without a need to handle such edge cases. I'm all for the introduction of I'll add this functionality with tests to the PR |
3d36341
to
96c0a16
Compare
74d2a77
to
fd1fbab
Compare
@Quillraven please review |
fd1fbab
to
782b8df
Compare
A little update. I cleaned up things a bit in the hook creation code and explicitly turned the hook-cached system list to an array. That way both family hooks and the world keep the systems in arrays. This will help later introduce a unified |
Use system array for family hooks.
71257c2
to
9241541
Compare
Cool stuff, thank you for all your effort and sorry that I can only review and give some comments at the moment to such bigger changes :( I have two minor remarks (see above) but besides that the PR is ready to be merged - good job! edit: hope you are happy with your |
No worries and thanks for your guidance! I like how polished and clean the project is and it is best to keep it that way. Totally worth it!
Absolutely, haha! I already tried the changes to the library on my project to achieve the cases I described and it works flawlessly! This probably will be that "breaking change" thing in the changelog, but I hope it will pay off and serve as a foundation for any future configuration improvements. |
This PR picks up on #123 and implements family hooks for
IteratingSystem
onAdd
/onRemove
hooks. So, just for the proof of concept, I went ahead and used the simplest approach (IteratingSystem.familyHooks
boolean flag). We can easily switch to any other approach, I have no strong preference for it.onAdd
hooks in forward order andonRemove
hooks in reverse.SystemFamilyHooksTest.kt
). It might be not the best example of a test, but it illustrates and covers the expected hook call order.@Quillraven please let me know what you think.