Moments of Inertia by Rachel Crawford

About     Archive     Categories     Tags     Feed     Projects    

Collections of References

What does this code do?

const auto it = FindByAddress(specialEntities, specialEntity);

In the game engine I’m working on, an Entity (or one of an Entity’s components) can sometimes be in a state where it needs to have some extra processing done. Rather than having a branch in Entity::Update, we can just store a pointer to the Entity/Component in a container along with all the others that meet the same criteria. This is existence-based processing.

For example, RenderComponents can be in a state where they own a fixture (shape) in the physics world which needs to be rotated to face the camera before rendering happens (don’t ask me why at the moment; it’s complicated). We refer to these special RenderComponents as ‘Flat Sprites’, and keep track of all of them in a std::vector of pointers:

std::vector<RenderComponent*> mFlatSprites;

It’s safe to do this because RenderComponent instances are dynamically allocated and do not move around in memory.

Just before rendering, we call RenderComponent::UpdateFlatSprite on each of the instances pointed to by the pointers in the container, rotating their fixture to face the camera. When adding or removing a RenderComponent from the set of Flat Sprites, we can use std::find to check for whether the pointer we’re trying to add or remove is already in the array:

// flatSprite is of type RenderComponent*
const auto it = std::find(mFlatSprites.begin(), mFlatSprites.end(), flatSprite);
if (it != mFlatSprites.end())
  // the pointer was found in mFlatSprites

Easy! Yet, there’s something a little bit smelly about all this: we’re using pointers instead of references.

(In case you’re unaware, references in C++ are just pointers with special semantics: they cannot (without coercion) point to null – they must always point to an actual instance. They also behave syntactically like values, so you can act upon them as you would a normal instance using the . operator instead of the -> operator (and dereference operator *) that you have to use with normal pointers.)

The problem with references is that this doesn’t compile:

std::vector<RenderComponent&> mFlatSprites;

This is (partly) because references cannot be default-initialised. That is, a reference must always be assigned a value in the place where it is initialised.

// invalid:
SomeType& someTypeRef;
// valid:
SomeType someTypeInst(/* blah */);
SomeType& someTypeRef = blah;

But I want an array of references, because I want to represent the idea that elements can never be null. Fortunately, I can do this by writing a structure that ‘wraps’ a reference, and creating an array of that. I don’t even have to write that wrapper myself, because it’s already a template in the standard library: std::reference_wrapper.

std::vector<std::reference_wrapper<RenderComponent>> mFlatSprites;

Stay with me now, because I’m closing in on the point of this post. This solution creates a new problem: what should the following code do?

// flatSprite is of type RenderComponent&
const auto it = std::find(mFlatSprites.begin(), mFlatSprites.end(), flatSprite);

Should it compare the instance each element in the array refers to with that referred-to by flatSprite? Or should it compare the values of the underlying pointers involved – the addresses at which the RenderComponents reside – which is what I want?

Turns out the implementers of std::reference_wrapper didn’t want to make that call for you, which is a good thing. The code does not compile because bool operator==(const std::reference_wrapper<RenderComponent>, const RenderComponent&) is undefined.

I could just write a definition for that operator myself and make it compare the addresses, but I decided not to. There may be cases down the line where I want that operator to compare the values rather than the addresses, and I want to make it explicit what I’m trying to do here. Finally we’re back to that first line of code.

const auto it = FindByAddress(specialEntities, specialEntity);
// except in this case it's actually:
const auto it = FindByAddress(mFlatSprites, flatSprite);

FindByAddress is defined like so:

auto FindByAddress(
  const std::vector<std::reference_wrapper<RenderComponent>>& v,
  const RenderComponent& rc)
{
	for (auto it = v.begin(); it != v.end(); ++it)
	{
		const auto deref = *it;

		if (&deref.get() == &rc)
		{
			return it;
		}
	}

	return v.end();
}

Which we can make more generic by turning it into a template:

template<typename T>
auto FindByAddress(
  const std::vector<std::reference_wrapper<T>>& v,
  const T& tRef)

In fact we could make it even more generic and not constrain it to only working with std::vectors by having it take two iterators, a begin and an end. Or, in the bright future, a range. I’ll leave that as an exercise for the reader.

I’ll also leave as an exercise for the reader the task of pointing out mistakes I’ve made, or why an array of references is a bad idea, or generally letting me know what you think of the code I’ve shared here either on Twitter or in the comments below. Can I really refer to my cakes and eat them?

Shout out to Elias Daler for his recent dev log, which alerted me to the existence of reference wrappers and started all of this.

Halo: Reach

Your friends die. You die. The planet dies. You don’t exactly ‘win the game’ at the end of Halo: Reach’s single-player campaign.

Watching the latest Star Wars film, Rogue One, I was struck by the similarity of its finale to that of Reach1. In Rogue One (spoilers, by the way), the heroes’ self-sacrifice allows Princess Leia to escape with the plans for the Death Star – the film ends almost immediately before A New Hope begins. In Reach, the heroes’ self-sacrifice allows the Pillar of Autumn to escape, with Cortana and the Master Chief aboard, and therefore find the first Halo ring. The last lines of Reach’s script are the first of Halo: Combat Evolved:

Captain Keyes: Cortana, all I need to know is did we lose them?

Cortana: I think we both know the answer to that.

Stories which deploy this conceit have to be prequels – at least, I can’t think of any examples which aren’t – because the only way you can get away with such a bleak ending is if the audience knows how it all works out okay in the end.

Read more...

San Francisco

I was recently in San Francisco for five-ish days to attend Game Developer’s Conference. It was my first time in an American city – previous trips to the US have taken me only to skiing resorts. We spent most of our time in and around the conference, and only really had time for a bit of proper sightseeing on Saturday.

It’s a nice place, but very different to what I’m used to. In Scotland we barely have any tall buildings, so when I’m surrounded on all sides by skyscrapers I felt a little bit like I was going to fall into the sky, which is the most country-bumpkin thing about myself I’ve ever admitted (on this blog).

Read more...

My First GDC

Game Developers Conference is an annual gathering of people involved in the game industry from all over the world. It takes place in and around the Moscone Centre in San Francisco, California. I’ve always wanted to go, but it’s expensive to get out there and get accommodation and very expensive for the kind of pass that gets you into lots of interesting talks and events, which is one of the main reasons to go.

Very luckily, and to my great surprise, this year I got to go for free (for reasons I’ll explain). I was in San Francisco from Tuesday 28th February until Sunday 5th March and have spent most of my free time since I got back recovering. Here are some words about my trip.

Read more...

Global Game Jam 2017

Last year I took part in Global Game Jam at Abertay University, and I had such a good time that this year I did it again.

The theme was ‘Waves’. I teamed up with Dziek Dyes Bolt, Callum Fowlie (two members of last year’s team) and Natalie Clayton to make ‘Si the Wave, Be the Wave’, a competitive local multiplayer game in which two players, each controlling a crowd of characters, must race to complete Mexican waves. You can check it out and download it on the Global Game Jam website (Windows only). The final version is a bit broken. If you’re feeling brave, the predictably turgid source code is available on GitHub.

The jam went a bit differently for me this time around. Unlike last year we actually had our mechanic working before the end of Saturday, rather than on Sunday morning, so we had time to rethink our idea and iterate upon it. However, we didn’t quite have enough work for everyone to do, and we had the usual difficulties one has when one tries to make a 2D game in Unity. I’m not quite as pleased with our game this time around – I think last year’s concept was very strong in comparison – but I still had a valuable experience trying new things and getting to write messy code, something that is quite refreshing after 5 months of programming as a day job.

The atmosphere at Abertay was fantastic once again, with the organisers (some of the lecturers from Arts, Media and Games) successfully creating a positive, healthy atmosphere. There’s always a lot of talk about staying up for the whole length of the jam and eating lots of junk food (the keynote video this time around even encouraged it) and I think it’s important to reject that attitude. It doesn’t lead to better games or developers. They also put emphasis on the process and not the product, which is how it should be.

And they gave everyone jam at the end.

A whole load of rad games got made. Check them out. Some highlights are MicroWaves, Ola De La Vida, A Kraken Adventure, Calling Mother, Ragnar The Flying Axe, E C H O C H A M B E R and Porcelain Pork Purloiner. Finally, my friends’ game Tiny Town Traffic Technician was my first try of the HTC Vive. I can definitely see the appeal!

There’s a Twitter hashtag if you wanna scroll through that.