Skip to content

Commit

Permalink
add systemOrNull and system contains
Browse files Browse the repository at this point in the history
  • Loading branch information
Quillraven committed Jul 7, 2024
1 parent a70d5d9 commit a9a2b7f
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 202 deletions.
216 changes: 14 additions & 202 deletions src/commonMain/kotlin/com/github/quillraven/fleks/world.kt
Original file line number Diff line number Diff line change
@@ -1,211 +1,10 @@
package com.github.quillraven.fleks

import com.github.quillraven.fleks.World.Companion.CURRENT_WORLD
import com.github.quillraven.fleks.collection.EntityBag
import com.github.quillraven.fleks.collection.MutableEntityBag
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import kotlin.native.concurrent.ThreadLocal
import kotlin.reflect.KClass

/**
* DSL marker for the [WorldConfiguration].
*/
@DslMarker
annotation class WorldCfgMarker

/**
* Wrapper class for injectables of the [WorldConfiguration].
* It is used to identify unused injectables after [world][World] creation.
*/
data class Injectable(val injObj: Any, var used: Boolean = false)

/**
* A DSL class to configure [Injectable] of a [WorldConfiguration].
*/
@WorldCfgMarker
class InjectableConfiguration(private val world: World) {

/**
* Adds the specified [dependency] under the given [name] which
* can then be injected via [World.inject].
*
* @throws [FleksInjectableAlreadyAddedException] if the dependency was already added before.
*/
fun <T : Any> add(name: String, dependency: T) {
if (name in world.injectables) {
throw FleksInjectableAlreadyAddedException(name)
}

world.injectables[name] = Injectable(dependency)
}

/**
* Adds the specified [dependency] via its [simpleName][KClass.simpleName],
* or via its [toString][KClass.toString] if it has no name.
* It can then be injected via [World.inject].
*
* @throws [FleksInjectableAlreadyAddedException] if the dependency was already added before.
*/
inline fun <reified T : Any> add(dependency: T) = add(T::class.simpleName ?: T::class.toString(), dependency)
}

/**
* A DSL class to configure [IntervalSystem] of a [WorldConfiguration].
*/
@WorldCfgMarker
class SystemConfiguration(
private val systems: MutableList<IntervalSystem>
) {
/**
* Adds the [system] to the [world][World].
* The order in which systems are added is the order in which they will be executed when calling [World.update].
*
* @throws [FleksSystemAlreadyAddedException] if the system was already added before.
*/
fun add(system: IntervalSystem) {
if (systems.any { it::class == system::class }) {
throw FleksSystemAlreadyAddedException(system::class)
}
systems += system
}
}

/**
* A DSL class to configure [FamilyHook] for specific [families][Family].
*/
@WorldCfgMarker
class FamilyConfiguration(
@PublishedApi
internal val world: World,
) {

/**
* Sets the add [hook][Family.addHook] for the given [family].
* This hook gets called whenever an [entity][Entity] enters the [family].
*/
fun onAdd(
family: Family,
hook: FamilyHook
) {
if (family.addHook != null) {
throw FleksHookAlreadyAddedException("addHook", "Family $family")
}
family.addHook = hook
}

/**
* Sets the remove [hook][Family.removeHook] for the given [family].
* This hook gets called whenever an [entity][Entity] leaves the [family].
*/
fun onRemove(
family: Family,
hook: FamilyHook
) {
if (family.removeHook != null) {
throw FleksHookAlreadyAddedException("removeHook", "Family $family")
}
family.removeHook = hook
}
}

/**
* A configuration for an entity [world][World] to define the systems, dependencies to be injected,
* [component][Component]- and [family][Family] hooks.
*
* @param world the [World] to be configured.
*/
@WorldCfgMarker
class WorldConfiguration(@PublishedApi internal val world: World) {

private var injectableCfg: (InjectableConfiguration.() -> Unit)? = null
private var familyCfg: (FamilyConfiguration.() -> Unit)? = null
private var systemCfg: (SystemConfiguration.() -> Unit)? = null

fun injectables(cfg: InjectableConfiguration.() -> Unit) {
injectableCfg = cfg
}

fun families(cfg: FamilyConfiguration.() -> Unit) {
familyCfg = cfg
}

fun systems(cfg: SystemConfiguration.() -> Unit) {
systemCfg = cfg
}

/**
* Sets the add entity [hook][EntityService.addHook].
* This hook gets called whenever an [entity][Entity] gets created and
* after its [components][Component] are assigned and [families][Family] are updated.
*/
fun onAddEntity(hook: EntityHook) {
world.setEntityAddHook(hook)
}

/**
* Sets the remove entity [hook][EntityService.removeHook].
* This hook gets called whenever an [entity][Entity] gets removed and
* before its [components][Component] are removed and [families][Family] are updated.
*/
fun onRemoveEntity(hook: EntityHook) {
world.setEntityRemoveHook(hook)
}

/**
* Sets the [EntityProvider] for the [EntityService] by calling the [factory] function
* within the context of a [World]. Per default the [DefaultEntityProvider] is used.
*/
fun entityProvider(factory: World.() -> EntityProvider) {
world.entityService.entityProvider = world.run(factory)
}

/**
* Configures the world in following sequence:
* - injectables
* - family
* - system
*
* The order is important to correctly trigger [FamilyHook]s and [EntityHook]s.
*/
fun configure() {
injectableCfg?.invoke(InjectableConfiguration(world))
familyCfg?.invoke(FamilyConfiguration(world))
SystemConfiguration(world.mutableSystems).also {
systemCfg?.invoke(it)
}

if (world.numEntities > 0) {
throw FleksWorldModificationDuringConfigurationException()
}

world.initAggregatedFamilyHooks()
world.systems.forEach { it.onInit() }
}
}

/**
* Creates a new [world][World] with the given [cfg][WorldConfiguration].
*
* @param entityCapacity initial maximum entity capacity.
* Will be used internally when a [world][World] is created to set the initial
* size of some collections and to avoid slow resizing calls.
*
* @param cfg the [configuration][WorldConfiguration] of the world containing the [systems][IntervalSystem],
* [injectables][Injectable] and [FamilyHook]s.
*/
fun configureWorld(entityCapacity: Int = 512, cfg: WorldConfiguration.() -> Unit): World {
val newWorld = World(entityCapacity)
CURRENT_WORLD = newWorld

try {
WorldConfiguration(newWorld).apply(cfg).configure()
} finally {
CURRENT_WORLD = null
}

return newWorld
}

/**
* Snapshot for an [entity][Entity] that contains its [components][Component] and [tags][EntityTag].
Expand Down Expand Up @@ -266,7 +65,7 @@ class World internal constructor(
get() = entityService.capacity

// internal mutable list of systems
// can be replaced in a later version of Kotlin with "backingfield" syntax
// can be replaced in a later version of Kotlin with "backing field" syntax
internal val mutableSystems = arrayListOf<IntervalSystem>()

/**
Expand Down Expand Up @@ -403,6 +202,19 @@ class World internal constructor(
throw FleksNoSuchSystemException(T::class)
}

/**
* Returns true if and only if the given [system] is part of the world.
*/
inline fun <reified T : IntervalSystem> contains(): Boolean {
return systems.any { it is T }
}

/**
* Returns the specified [system][IntervalSystem] or null if there is no such system.
*/
inline fun <reified T : IntervalSystem> systemOrNull(): T? {
return systems.firstOrNull { it is T } as T?
}

/**
* Adds the [system] to the world's [systems] at the given [index].
Expand Down
24 changes: 24 additions & 0 deletions src/commonTest/kotlin/com/github/quillraven/fleks/WorldTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,30 @@ internal class WorldTest {
assertFailsWith<FleksNoSuchSystemException> { w.system<WorldTestIntervalSystem>() }
}

@Test
fun testSystemOrNull() {
val world = configureWorld {
systems {
add(WorldTestIntervalSystem())
}
}

assertNotNull(world.systemOrNull<WorldTestIntervalSystem>())
assertNull(world.systemOrNull<WorldTestIteratingSystem>())
}

@Test
fun testSystemContains() {
val world = configureWorld {
systems {
add(WorldTestIntervalSystem())
}
}

assertTrue(world.contains<WorldTestIntervalSystem>())
assertFalse(world.contains<WorldTestIteratingSystem>())
}

@Test
fun cannotCreateSystemWhenInjectablesAreMissing() {
assertFailsWith<FleksNoSuchInjectableException> {
Expand Down

0 comments on commit a9a2b7f

Please sign in to comment.