We need to improve collision detection. I think we have to do it in one go.

We noticed last time that the collision-finding logic is messy, and we made it more so:

function Universe:findCollisions()
    -- we clone the asteroids collection to allow live editing
    local needNewWave = true
    for i,a in pairs(clone(self.asteroids)) do
        needNewWave = false
        if self.ship then self:checkShipCollision(a) end
    if needNewWave == true then
        if self.timeOfNextWave == 0 then
            self.timeOfNextWave = self.currentTime + self.timeBetweenWaves

function Universe:checkShipCollision(asteroid)
    if self.ship.pos:dist(asteroid.pos) < asteroid:killDist() then

function Universe:checkMissileCollisions(asteroid)
    for k,m in pairs(self.missiles) do
        if self.ship and m.pos:dist(self.ship.pos) < self.ship:killDist() then
        if m.pos:dist(asteroid.pos) < asteroid:killDist() then

The original plan was just to compare asteroids and missiles … then we added in the ship, the the saucer, and the logic got more and more strange. Part of the motivation was that the complexity just grew – true technical debt – and part was a desire not to have to compare every object with every other.

The result so far includes several different collections of objects: asteroids, missiles, ships (there’s only one), saucers (only one), and these collections are managed separately. And we have logic in Universe deciding whether things have collided, which is quite arguably up to them. We’ve had to handle the collections with kid gloves, whatever that means, protecting against premature object death by cloning the asteroids collection, after adding and destroying in mid loop uncovered a very mysterious Codea anomaly.

It’s time to fix this, and I have a plan. It’s too big and it goes like this:

  • Keep all the objects in a single collection;
  • for collision-checking use a double loop over the objects
  • ask each pair to decide what to do about colliding
  • (possibly deciding in the loop whether collision is possible)
  • use double-dispatch to leave handling collisions up to the parties involved
  • build lists of added objects and objects to be deleted as we go
  • apply deletes and adds after the loops are complete

Let me give a bit more detail of the double-dispatch idea.

The two loops over objects are looping over all the objects and (if the plan holds up) they will just ask each pair to decide if they’re colliding and what to do about it. Suppose at some point we have in hand a missile, m1, and another missile, m2. We send:


collide is implemented the same in every object:

function XXX:collide(anObject)

So m1 receives collide(m2) and sends


Missiles don’t destroy each other (at least not now) so

function Missile:collideWithMissile(aMissile)

Nothing happens. Suppose instead we have an asteroid a and a missile m, and they are in fact close enough to collide. We send:


and then a sends


That’s implemented, roughly:

function Missile:collideWithAsteroid(a)
  if we're not close enough to a to kill it then return


function Asteroid:hitByMissileMissile(m)
  U:add new asteroids if needed

The upshot of this is that every object will explicitly know what to do when it is in range of or hit by another object, based on what kind of object it is … without anyone checking objects for type.

This may seem rather tricky to you, but it is the standard way to deal with interactions between two objects of different types. Tell one “intaractWith”, it tells the other interactWithMyType and now there’s a place for code based on both types without ever checking.

I’m sure it may seem weird, but I’m also sure by the time it’s done we’ll be happy with it in every way. Or, if not, we’ll have learned something, even if it’s just that I like it and you don’t.

It’s a thing to know how to do, anyway.

My concern is that, at first glance, I don’t see how to do it incrementally, but doing it all at once feels too big to get done in one go. We have a couple of options.

First, we could just go for it. Rip out the old logic, put in the new logic, and just keep going until done. I think we’re likely to run out of time for that. What do I mean? I mean that so far, every couple of hour session has ended with the program running a little bit better than the time before. It is a fundamental notion of mine that that is always possible. Here I have my doubts.

Second, we could figure out a way to do this in smaller steps. At this moment, I don’t quite see that, but it seems safer. Let’s think about that:

What are some small steps we could take?

Make the master collection
It should be easy to make the big collection of objects since we are already making all the little ones. We could just add everything to the big collection in those locations. Then later we could remove the others.
Build collision logic incrementally
We have a loop that checks missiles against an asteroid. We could change that one to do the missile:collide(asteroid) thing, and leave it in place, moving on to the next possibility. That would let us sort out a single case that could serve as a pattern for the others. And I expect we’ll find out something about my raw idea that isn’t quite right.

We can probably do all of asteroid::missile, ship::missile, and ship::asteroid without unwinding the logic shown above.

Do deletes and adds uniformly?
We can certainly build the delete and add lists during this process. Whether we can apply them is a question. When we go to the big collection, we can do all the removing and adding. But in the interim, we can’t go fully to using those lists unless we want to do something fancy, like check the types and put them into the right collection. That would be doable but tacky. We’d have to decide in the moment whether to do that or
At some point, bite the bullet and go for it
With the major interactions all done, we just have the loop replacement and all the interactions that do nothing, like asteroid::asteroid and missile::missile. (Unless we want to be able to shoot down missiles.) Sooner or later we have to do the big double loop and finish up. With luck, it’ll be a short reach by then.

There are other directions we could go in:

Make the big collection
As before, we can create this incrementally, maintaining the old collections.
Loop over the big collection, checking type
We presently have one major loop over asteroids, another over missiles. We could change those to use the big collection and select only asteroids for the one, only missiles for the other. Then relax the constraints slowly, type by type.

I’m not clear on how this one might be done, but it’s certainly able to be done.

But wait, there’s more

When all this is done, our drawing logic gets much simpler: we draw everything, move everything, collide everything. I think that ideally, we do that with three loops over all the objects, with only the third being nested.

In the end, everything will be much nicer, I’m sure of it. The trick is to move incrementally, not breaking anything, keeping the program running all the time.

We have to pick a way. I think a decent first step is to create the new collection objects and start getting everyone into it. That’s surely doable without harm.

I’ll start that now, even though it’s 3:20 AM.

Big collection

I’ll start by creating the collection in Universe:init:

function Universe:init()
    self.processorRatio = 1.0
    self.score = 0
    self.rotationStep = math.rad(1.5) -- degrees
    self.missileVelocity = vec2(MissileSpeed,0)
    self.frame64 = 0
    self.saucerInterval = 2
    self.timeBetweenWaves = 2
    self.timeOfNextWave= 0
    self.objects = {} -- <--- new
    self.button = {}
    self.asteroids = {}
    self.missiles = {}
    self.explosions = {}
    self.attractMode = true

By the way, I don’t think the buttons will go into objects everything else there will.

This code clearly runs, lets just make sure everyone adds to objects as well as their current home:

function Universe:addAsteroid(asteroid)
    self.objects[asteroid] = asteroid
    self.asteroids[asteroid] = asteroid

function Universe:deleteAsteroid(asteroid)
    self.objects[asteroid] = nil
    self.asteroids[asteroid] = nil

function Universe:addMissile(missile)
    self.objects[missile] = missile
    self.missiles[missile] = missile

function Universe:deleteMissile(missile)
    self.objects[missile] = nil
    self.missiles[missile] = nil

function Universe:addSaucer(saucer)
    self.objects[saucer] = saucer
    self.saucer = saucer

function Universe:deleteSaucer(saucer)
    self.objects[saucer] = nil
    self.saucer = nil
    self.saucerTime = self.currentTime

We need to look at Splat, and Explosion, to see how to handle them.

Universe calls global drawSplats, which is maintained by the Splat class. I think we can leave that be for now and fold it in later or never.

Explosion is a hybrid. It’s a class and Universe has the collection, but Explosion is still setting things into that collection:

function Explosion:init(ship)
    local f = function()
        U.explosions[self] = nil
    self.pos = ship.pos
    self.step = vec2(0,0)
    U.explosions[self] = self
    tween(4, self, {}, tween.easing.linear, f)

Let’s test and commit what we have now, and then change Explosion.

We get an error when the Saucer times out:

Universe:117: table index is nil
stack traceback:
	Universe:117: in method 'deleteSaucer'
	Saucer:46: in method 'die'
	Saucer:5: in field 'callback'
	...in pairs(tweens) do
    c = c + 1
  return c

I’m not dead certain why that has happened but what it means is that when we say:

    self.objects[saucer] = nil

the saucer parameter is nil, so we can’t put it away. Yes, well:

function Saucer:die()

It certainly is nil. Fix:

function Saucer:die()

We didn’t need that before because we just nilled the universe’s pointer to the saucer.

Now everything is working. Commit: “object collection for asteroids, missiles, saucer”.

That should do it for tonight. We’ve started the refactoring, made some progress, and have a running system again. I’ll include the zip file but its behavior is the same as last time. That’s refactoring for you. :)

Zipped Code