Skip to content

Commit

Permalink
Validate non-iterating systems for family hook illegal interfaces.
Browse files Browse the repository at this point in the history
Merge family hook system tests to the main system test file.
  • Loading branch information
metaphore committed Nov 28, 2023
1 parent 4a585fe commit 031010b
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ class FleksWrongConfigurationUsageException :
"The global functions 'inject' and 'family' must be used inside a WorldConfiguration scope." +
"The same applies for 'compareEntityBy' and 'compareEntity' unless you specify the world parameter explicitly."
)

class FleksWrongSystemInterfaceException(system: KClass<*>, `interface`: KClass<*>) :
FleksException("System $system cannot have interface $`interface`")
13 changes: 13 additions & 0 deletions src/commonMain/kotlin/com/github/quillraven/fleks/world.kt
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,19 @@ class WorldConfiguration(@PublishedApi internal val world: World) {
*/
private fun setUpAggregatedFamilyHooks() {

// Validate systems against illegal interfaces.
world.systems.forEach { system ->
// FamilyOnAdd and FamilyOnRemove interfaces are only meant to be used by IteratingSystem.
if (system !is IteratingSystem) {

if (system is IteratingSystem.FamilyOnAdd)
throw FleksWrongSystemInterfaceException(system::class, IteratingSystem.FamilyOnAdd::class)

if (system is IteratingSystem.FamilyOnRemove)
throw FleksWrongSystemInterfaceException(system::class, IteratingSystem.FamilyOnRemove::class)
}
}

// Register family hooks for IteratingSystem.FamilyOnAdd containing systems.
world.systems
.mapNotNull { system -> if (system is IteratingSystem && system is IteratingSystem.FamilyOnAdd) system else null }
Expand Down

This file was deleted.

100 changes: 100 additions & 0 deletions src/commonTest/kotlin/com/github/quillraven/fleks/SystemTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ private data class SystemTestComponent(
companion object : ComponentType<SystemTestComponent>()
}

private data class NameComponent(
val name: String
) : Component<NameComponent> {
companion object : ComponentType<NameComponent>()
override fun type() = NameComponent
}

private class SystemTestIteratingSystem : IteratingSystem(
family = family { all(SystemTestComponent) },
interval = Fixed(0.25f)
Expand Down Expand Up @@ -431,4 +438,97 @@ internal class SystemTest {
val system = world.system<SystemTestEntityCreation>()
assertEquals(1, system.numTicks)
}


@Test
fun iterationSystemFamilyHookFlow() {
// this verifies that IterationSystems properly subscribe to their family's onAdd/onRemove hooks
// and that the order of hook calls is correct

var check: Int = 0

val world = configureWorld {
families {
val family = family { all(NameComponent) }
onAdd(family) { entity ->
assertEquals(0, check++) // First, we expect the global add hook to be called.
println("$check. Global family onAdd() called with: entity = ${entity[NameComponent].name}")
}
onRemove(family) { entity ->
assertEquals(5, check++) // The global hook gets called before the systems.
println("$check. Global family onRemove() called with: entity = ${entity[NameComponent].name}")
}
}

systems {
add(object : IteratingSystem(family { all(NameComponent) }), IteratingSystem.FamilyOnAdd, IteratingSystem.FamilyOnRemove {
val sysName = "System0"

override fun onAddEntity(entity: Entity) {
assertEquals(1, check++) // onAdd hooks called in forward system order. Thus, "System0" gets called first.
println("$check. $sysName onAddEntity() called with: entity = ${entity[NameComponent].name}")
}

override fun onTickEntity(entity: Entity) {
assertEquals(3, check++)
println("$check. $sysName onTickEntity() called with: entity = ${entity[NameComponent].name}")
}

override fun onRemoveEntity(entity: Entity) {
assertEquals(6, check++)
println("$check. $sysName onRemoveEntity() called with: entity = ${entity[NameComponent].name}")
}
})
add(object : IteratingSystem(family { all(NameComponent) }), IteratingSystem.FamilyOnAdd, IteratingSystem.FamilyOnRemove {
val sysName = "System1"

override fun onAddEntity(entity: Entity) {
assertEquals(2, check++)
println("$check. $sysName onAddEntity() called with: entity = ${entity[NameComponent].name}")
}

override fun onTickEntity(entity: Entity) {
assertEquals(4, check++)
println("$check. $sysName onTickEntity() called with: entity = ${entity[NameComponent].name}")
}

override fun onRemoveEntity(entity: Entity) {
assertEquals(7, check++) // Lastly, the system with the highest order receives the hook call.
println("$check. $sysName onRemoveEntity() called with: entity = ${entity[NameComponent].name}")
}
})
}
}
val entity = world.entity { it += NameComponent("Entity0") }
world.update(0f)
world -= entity
}

@Test
fun illegalFamilyOnAddInterfaceOnIntervalSystem() {
assertFailsWith<FleksWrongSystemInterfaceException> {
configureWorld {
systems {
add(object : IntervalSystem(), IteratingSystem.FamilyOnAdd {
override fun onTick() = Unit
override fun onAddEntity(entity: Entity) = Unit
})
}
}
}
}

@Test
fun illegalFamilyOnRemoveInterfaceOnIntervalSystem() {
assertFailsWith<FleksWrongSystemInterfaceException> {
configureWorld {
systems {
add(object : IntervalSystem(), IteratingSystem.FamilyOnRemove {
override fun onTick() = Unit
override fun onRemoveEntity(entity: Entity) = Unit
})
}
}
}
}
}

0 comments on commit 031010b

Please sign in to comment.