GitHub Decentralized Repo
GitHub Centralized Repo
GitHub Flat Repo

Let’s start moving our new Timers into a separate table. That should be “easy”.

I figure I’ll just build a top-level TimerTable sort of thing and add Timers into it from addComponent. I’ll leave them as components in the entity as well, which will keep things running as we transition, then move them out after the table works.

var Score: Int = 0
lateinit var SpaceObjects: Array<SpaceObject>
lateinit var Ship: SpaceObject
lateinit var Saucer: SpaceObject
var TimerTable: List<Timer> = mutableListOf<Timer>()

Then:

fun addComponent(entity: SpaceObject, component: Component) {
    entity.components.add(component)
    if (component is Timer ) TimerTable += component
}

Should leave me green. Now let’s change this:

private fun updateEverything(
    spaceObjects: Array<SpaceObject>,
    deltaTime: Double,
    width: Int,
    height: Int
) {
    for (spaceObject in spaceObjects) {
        for (component in spaceObject.components) update(component, deltaTime)
        if (spaceObject.type == SpaceObjectType.SHIP) applyControls(spaceObject, deltaTime)
        move(spaceObject, width, height, deltaTime)
    }
}

fun update(component: Component, deltaTime: Double) {
    when (component) {
        is SaucerTimer -> {
            updateSaucerTimer(component, deltaTime)
        }
        is Timer -> {
            with(component) {
                if (entity.active == processWhenActive) {
                    time -= deltaTime
                    if (time <= 0.0) {
                        action(this)
                        time = delayTime
                    }
                }
            }
        }
    }
}

We want not to update the Timer in update, and to do it separately in updateEverything. Like this:

private fun updateEverything(
    spaceObjects: Array<SpaceObject>,
    deltaTime: Double,
    width: Int,
    height: Int
) {
    for (timer in TimerTable) {
        with(timer) {
            if (entity.active == processWhenActive) {
                time -= deltaTime
                if (time <= 0.0) {
                    action(this)
                    time = delayTime
                }
            }
        }
    }
    for (spaceObject in spaceObjects) {
        for (component in spaceObject.components) update(component, deltaTime)
        if (spaceObject.type == SpaceObjectType.SHIP) applyControls(spaceObject, deltaTime)
        move(spaceObject, width, height, deltaTime)
    }
}

I don’t really like that much nesting, but I think we want that written out in long form for best memory usage? I think tests will break now, because the Timer isn’t being called in update. Let me see if the game works.

The game works fine. I’m truly sorry but I need to break out a function from that update, so that I can test. And the nesting drives me up the wall.

private fun updateEverything(
    spaceObjects: Array<SpaceObject>,
    deltaTime: Double,
    width: Int,
    height: Int
) {
    for (timer in TimerTable) {
        updateTimer(timer, deltaTime)
    }
    for (spaceObject in spaceObjects) {
        for (component in spaceObject.components) update(component, deltaTime)
        if (spaceObject.type == SpaceObjectType.SHIP) applyControls(spaceObject, deltaTime)
        move(spaceObject, width, height, deltaTime)
    }
}

fun updateTimer(timer: Timer, deltaTime: Double) {
    with(timer) {
        if (entity.active == processWhenActive) {
            time -= deltaTime
            if (time <= 0.0) {
                action(this)
                time = delayTime
            }
        }
    }
}

Now all those calls saying update(timer can say updateTimer(timer. Changing those, plus adjusting the test that ensures you only get four missiles at a time results in green.

Commit: Timers are now kept in TimerTable and processed all at once. Step toward TimerSystem.

Now I can remove the addition of the Timer component to the entity, changing this:

fun addComponent(entity: SpaceObject, component: Component) {
    entity.components.add(component)
    if (component is Timer ) TimerTable += component
}

To this:

fun addComponent(entity: SpaceObject, component: Component) {
    if (component is Timer ) TimerTable += component
    else entity.components.add(component)
}

Should be green. A test fails that was looking for the timer in the components:

    @Test
    fun `timer resets on deactivate`() {
        val missile = newMissile()
        missile.active = true
        val timer = missile.components.find { it is Timer }!! as Timer
        val originalTime = timer.time
        updateTimer(timer, 0.5)
        assertThat(timer.time).describedAs("didn't tick down").isEqualTo(originalTime - 0.5, within(0.01))
        deactivate(missile)
        assertThat(timer.time).describedAs("didn't reset").isEqualTo(originalTime)
    }

I can search for that in the TimerTable but I suspect there’s more than one, because I’m never clearing it.

Let’s do this:

    @Test
    fun `timer resets on deactivate`() {
        TimerTable = emptyList()
        val missile = newMissile()
        missile.active = true
        val timer = TimerTable.first()
        val originalTime = timer.time
        updateTimer(timer, 0.5)
        assertThat(timer.time).describedAs("didn't tick down").isEqualTo(originalTime - 0.5, within(0.01))
        deactivate(missile)
        assertThat(timer.time).describedAs("didn't reset").isEqualTo(originalTime)
    }

Got the “didn’t reset message”. That’s an honest error, need to fix deactivate:

fun deactivate(entity: SpaceObject) {
    entity.active = false
    for (component in entity.components) {
        when (component) {
            is Timer -> {
                component.time = component.delayTime
            }
            is SaucerTimer -> {
                component.time = U.SaucerDelay
            }
        }
    }
    if (entity.type == SpaceObjectType.SHIP) shipGoneFor = 0.0
}

We’ll have to fetch its Timer from the TimerTable now. I’m really glad I virtuously wrote that test.

Now of course we could save the components in the object for just this occasion. But let’s not.

fun deactivate(entity: SpaceObject) {
    entity.active = false
    for (component in entity.components) {
        when (component) {
            is SaucerTimer -> {
                component.time = U.SaucerDelay
            }
        }
    }
    for (timer in TimerTable) {
        if (timer.entity == entity ) timer.time = timer.delayTime
    }
    if (entity.type == SpaceObjectType.SHIP) shipGoneFor = 0.0
}

Green. Commit: adjust deactivate to use TimerTable. Remove Timer from component list in SpaceObject.

Let’s sum up.

Summary

We aren’t quite to the point where we can say we have a System … or are we? We have a separate table of Timers, which we process in a batch. (We have broken out the code in the loop, to allow for easier testing, and perhaps a performance fanatic would not do that, but I’d rather have the tests.)

The change from built-in component to separate table went smoothly, never really breaking anything serious, but we did need to adjust some tests. That’s no surprise, since we changed a pretty central design decision about where the Timers live.

I like it. See you next time!